Asp.Net core Swashbuckle set operationId - asp.net-core

How can I set swagger operationId attribute in Asp.Net Core 2.1 project? According to this post I should use SwaggerOperationAttribute but I cannot find it in Swashbuckle.AspNetCore library. Also there is an IOperationFilter
public interface IOperationFilter
{
void Apply(Operation operation, OperationFilterContext context);
}
and I can't find any implementations for swagger generation purposes.

There are 2 other options without having to write any extra code or add extra dependency like Swashbuckle.AspNetCore.Annotations
Option 1: Convention based - SwaggerGen has an option to set CustomOperationIds. So you can simply set it to use ControllerName_HttpMethod like this:
services.AddSwaggerGen(c =>
{
c.CustomOperationIds(e => $"{e.ActionDescriptor.RouteValues["controller"]}_{e.HttpMethod}");
c.SwaggerDoc("v1", new Info { Title = "Test API", Version = "v1" });
});
This will add operationIds to all your methods, following ControllerName_HttpMethod convention.
Option 2: ActionFilter/Attribute based - you can configure each Action method (as you'd do with SwaggerOperation action filter by simple adding a Name property to your HTTP verb action filter like this:
[HttpPost(Name="Post_Person")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<ActionResult<Response>> PostAsync([FromBody]Request request)
{
Response result = await _context.PostAsync(request);
return Ok(result);
}
This works exactly like [SwaggerOperation(OperationId = "Post_Person")] but without the need of EnableAnnotations

Adding a Name parameter to [HttpGet]/[HttpPost] fails with an exception in the most recent version, but putting a Name parameter on the Route attribute seems to work:
/// <summary>
/// Get all devices
/// </summary>
[Route("devices", Name = "GetAllDevices")]
[Authorize]
[HttpGet]
[Produces(typeof(Device[]))]
public async Task<IActionResult> GetAllDevices() { ...}

You can also generate the operation id based on the action name which is the method name, I found this handy when generating the API client.
services.AddSwaggerGen(c =>
{
c.CustomOperationIds(e => $"{e.ActionDescriptor.RouteValues["action"]}");
c.SwaggerDoc("v1", new Info { Title = "Test API", Version = "v1" });
});

Its pretty simple
Add EnableAnnotations in ConfigureService Method
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Project HTTP API",
Version = "v1",
Description = "...."
});
options.EnableAnnotations();
});
And use in controllers
[SwaggerOperation(OperationId = "GET_API")]
You can see in this in Swagger Json as
get": {
"tags": [
"API"
],
"summary": "....",
"operationId": "GET_API"
}

You can enable annotation on swagger with the Swashbuckle.AspNetCore.Annotations NuGet package. (https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md#swashbuckleaspnetcoreannotations)
Once annotations have been enabled, you can enrich the generated Operation metadata by decorating actions with a SwaggerOperationAttribute.
[HttpPost]
[SwaggerOperation(
Summary = "Creates a new product",
Description = "Requires admin privileges",
OperationId = "CreateProduct",
Tags = new[] { "Purchase", "Products" }
)]
public IActionResult Create([FromBody]Product product)

Finally, I came to this solution:
public class SwaggerOperationNameFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
operation.OperationId = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<SwaggerOperationAttribute>()
.Select(a => a.OperationId)
.FirstOrDefault();
}
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class SwaggerOperationAttribute : Attribute
{
public SwaggerOperationAttribute(string operationId)
{
OperationId = operationId;
}
public string OperationId { get; }
}
// Startup.cs
services.AddSwaggerGen(c =>
{
...
c.OperationFilter<SwaggerOperationNameFilter>();
};
[HttpGet("{id:int}")]
[SwaggerOperation("GetById")]
public async Task<IActionResult> Get(int id)
{
...
}
But it still seems to me that I've reinvented the wheel.

add this line -
swagger.CustomOperationIds(e => $"{e.RelativePath}");
in services.AddSwaggerGen function call

Related

Conflicting method/path combination for action - Swagger unable to distinguish alternate version from Route

I have the following controller setup in my solution:
[Route("api/v{VersionId}/[controller]")]
[ApiController]
[Produces("application/json")]
[Consumes("application/json")]
public class MyBaseController : ControllerBase
{
}
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class AuthenticationController : MyBaseController
{
private readonly ILoginService _loginService;
public AuthenticationController(ILoginService loginService)
{
_loginService = loginService;
}
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[HttpPost("login")]
public ActionResult<v1.JwtTokenResponse> Login([FromBody] v1.LoginRequest loginRequest)
{
var loginResult = _loginService.Login(loginRequest.Email, loginRequest.Password);
if (loginResult.StatusCode != HttpStatusCode.OK)
{
return StatusCode((int)loginResult.StatusCode);
}
var tokenResponse = new v1.JwtTokenResponse() { Token = loginResult.Token };
return Ok(tokenResponse);
}
}
Between the two versions of my API, nothing has changed for this method and so logically in my documentation I want to display that the method is still supported in the new version. Let's argue that we have a second controller of customer that has had some changed logic and hence is the reason why we have the new version 1.1 as semantic versioning dictates something new has been added but in a backwards compatible manner.
When running this code, naturally everything builds fine. The code is valid and .net core allows this sort of implementation however, when it comes to the swagger gen I am hitting issues with it producing the following error:
NotSupportedException: Conflicting method/path combination "POST api/v{VersionId}/Authentication/login" for actions - Template.Api.Endpoints.Controllers.AuthenticationController.Login (Template.Api.Endpoints),Template.Api.Endpoints.Controllers.AuthenticationController.Login (Template.Api.Endpoints). Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
As you can see above, the path is different because the version parameter passed into the route makes it that way. Furthermore, it does not make sense to create a brand new method purely to represent that the code is available through documentation so, my question is why is swagger ignoring the version differences in the path and suggesting the user of the ConflictingActionsResolver?
Furthermore, after digging into this further and seeing that a lot of other people were having the same issue (with header versioning being a particular bugbear of the community and Swaggers hard-line approach being in conflict with this) the general approach seems to be to using the conflicting actions resolver to only take the first description it comes across which would only expose version 1.0 in the api documentation and leave out the 1.1 version giving the impression in Swagger that there is no 1.1 version of the endpoint available.
Swagger UI Config
app.UseSwaggerUI(setup =>
{
setup.RoutePrefix = string.Empty;
foreach (var description in apiVersions.ApiVersionDescriptions)
{
setup.SwaggerEndpoint($"/swagger/OpenAPISpecification{description.GroupName}/swagger.json",
description.GroupName.ToUpperInvariant());
}
});
How can we get around this and correctly display available endpoints in Swagger without having to create new methods that effectively result in a duplication of code just to satisfy what seems like an oversight in the Swagger spec? Any help would be greatly appreciated.
N.B. Many may suggest appending action on to the end of the route however we wish to avoid this as it would mean our endpoints are not restful where we want to strive for something like customers/1 with the GET, POST, PUT attributes deriving the CRUD operations without having to append something like customers/add_customer_1 or customers/add_customer_2 reflecting the method name in the URL.
This is my Swagger settings when using HeaderApiVersionReader.
public class SwaggerOptions
{
public string Title { get; set; }
public string JsonRoute { get; set; }
public string Description { get; set; }
public List<Version> Versions { get; set; }
public class Version
{
public string Name { get; set; }
public string UiEndpoint { get; set; }
}
}
In Startup#ConfigureServices
services.AddApiVersioning(apiVersioningOptions =>
{
apiVersioningOptions.AssumeDefaultVersionWhenUnspecified = true;
apiVersioningOptions.DefaultApiVersion = new ApiVersion(1, 0);
apiVersioningOptions.ReportApiVersions = true;
apiVersioningOptions.ApiVersionReader = new HeaderApiVersionReader("api-version");
});
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(swaggerGenOptions =>
{
var swaggerOptions = new SwaggerOptions();
Configuration.GetSection("Swagger").Bind(swaggerOptions);
foreach (var currentVersion in swaggerOptions.Versions)
{
swaggerGenOptions.SwaggerDoc(currentVersion.Name, new OpenApiInfo
{
Title = swaggerOptions.Title,
Version = currentVersion.Name,
Description = swaggerOptions.Description
});
}
swaggerGenOptions.DocInclusionPredicate((version, desc) =>
{
if (!desc.TryGetMethodInfo(out MethodInfo methodInfo))
{
return false;
}
var versions = methodInfo.DeclaringType.GetConstructors()
.SelectMany(constructorInfo => constructorInfo.DeclaringType.CustomAttributes
.Where(attributeData => attributeData.AttributeType == typeof(ApiVersionAttribute))
.SelectMany(attributeData => attributeData.ConstructorArguments
.Select(attributeTypedArgument => attributeTypedArgument.Value)));
return versions.Any(v => $"{v}" == version);
});
swaggerGenOptions.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));
//... some filter settings here
});
In Startup#Configure
var swaggerOptions = new SwaggerOptions();
Configuration.GetSection("Swagger").Bind(swaggerOptions);
app.UseSwagger(option => option.RouteTemplate = swaggerOptions.JsonRoute);
app.UseSwaggerUI(option =>
{
foreach (var currentVersion in swaggerOptions.Versions)
{
option.SwaggerEndpoint(currentVersion.UiEndpoint, $"{swaggerOptions.Title} {currentVersion.Name}");
}
});
appsettings.json
{
"Swagger": {
"Title": "App title",
"JsonRoute": "swagger/{documentName}/swagger.json",
"Description": "Some text",
"Versions": [
{
"Name": "2.0",
"UiEndpoint": "/swagger/2.0/swagger.json"
},
{
"Name": "1.0",
"UiEndpoint": "/swagger/1.0/swagger.json"
}
]
}
}
There are a couple of problems.
The first issue is that the route template does not contain the route constraint. This is required when versioning by URL segment.
Therefore:
[Route("api/v{VersionId}/[controller]")]
Should be:
[Route("api/v{VersionId:apiVersion}/[controller]")]
Many examples will show using version as the route parameter name, but you can use VersionId or any other name you want.
The second problem is that you are probably creating a single OpenAPI/Swagger document. The document requires that every route template is unique. The default behavior in Swashbuckle is a document per API version. This method will produce unique paths. If you really want a single document, it is possible using URL segment versioning, but you need to expand the route templates so they produce unique paths.
Ensure your API Explorer configuration has:
services.AddVersionedApiExplorer(options => options.SubstituteApiVersionInUrl = true);
This will produce paths that expand api/v{VersionId:apiVersion}/[controller] to api/v1/Authentication and api/v1.1/Authentication respectively.

CustomActionFilter not getting called for POST/PUT endpoint in web api [duplicate]

I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}

Unable to create swagger.json file when using aspnet-api-versioning

I have .NET Core 2.2 application. I am trying to set up API with different versions using Microsoft.AspnetCore.Mvc.Versioning nugetpackage. I followed the samples provided in the repository.
I want to use an API version based on the name of the defining controller's namespace.
Project Structure
Controllers
namespace NetCoreApiVersioning.V1.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("v{version:apiVersion}/[controller]")]
public class HelloWorldController : ControllerBase
{
public IActionResult Get()
{
return Ok();
}
}
}
namespace NetCoreApiVersioning.V2.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("v{version:apiVersion}/[controller]")]
public class HelloWorldController : ControllerBase
{
public IActionResult Get()
{
return Ok();
}
}
}
Note the controllers does not have [ApiVersion] attribute becuase i want the versioning to be defined by the namespace
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddApiVersioning(
options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
// automatically applies an api version based on the name of the defining controller's namespace
options.Conventions.Add(new VersionByNamespaceConvention());
});
services.AddVersionedApiExplorer(
options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "API v1 ", Version = "v1" });
c.SwaggerDoc("v2", new Info { Title = "API v2", Version = "v2" });
});
// commented code below is from
// https://github.com/microsoft/aspnet-api-versioning/tree/master/samples/aspnetcore/SwaggerSample
//services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
//services.AddSwaggerGen(
// options =>
// {
// // add a custom operation filter which sets default values
// //options.OperationFilter<SwaggerDefaultValues>();
// // integrate xml comments
// //options.IncludeXmlComments(XmlCommentsFilePath);
// });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
// remaining configuration omitted for brevity
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
app.UseMvc();
}
}
Issue
It is not able to generate swagger.json file. When i browse url /swaggger i see error undefined /swagger/v1/swagger.json
found..
i am missing [HttpGet] attribute in ActionMethods

How can I customize the error response in Web API with .NET Core?

I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}

FluentValidation with IActionFilter in Asp.net Core 2.1 [duplicate]

I have a logic to apply in case the request received is a BadRequest, to do this I have created a filter:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
// Apply logic
}
}
}
In Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => { options.Filters.Add<ValidateModelAttribute>(); });
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class VerifyController : ControllerBase
{
[Route("test")]
[HttpPost]
[ValidateModel]
public ActionResult<Guid> validationTest(PersonalInfo personalInfo)
{
return null;
}
}
Model:
public class PersonalInfo
{
public string FirstName { get; set; }
[RegularExpression("\\d{4}-?\\d{2}-?\\d{2}", ErrorMessage = "Date must be properly formatted according to ISO 8601")]
public string BirthDate { get; set; }
}
The thing is when I put a break point on the line:
if (!context.ModelState.IsValid)
execution reaches this line only if the request I send is valid. Why it is not passing the filter if I send a bad request?
The [ApiController] attribute that you've applied to your controller adds Automatic HTTP 400 Responses to the MVC pipeline, which means that your custom filter and action aren't executed if ModelState is invalid.
I see a few options for affecting how this works:
Remove the [ApiController] attribute
Although you can just remove the [ApiController] attribute, this would also cause the loss of some of the other features it provides, such as Binding source parameter inference.
Disable only the Automatic HTTP 400 Responses
Here's an example from the docs that shows how to disable just this feature:
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// ...
options.SuppressModelStateInvalidFilter = true;
// ...
}
This code goes inside of your Startup's ConfigureServices method.
Customise the automatic response that gets generated
If you just want to provide a custom response to the caller, you can customise what gets returned. I've already described how this works in another answer, here.
An example of intersection for logging is describe in Log automatic 400 responses
Add configuration in Startup.ConfigureServices.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Startup>>();
// Perform logging here.
//E.g. logger.LogError($”{context.ModelState}”);
logger.LogWarning(context.ModelState.ModelStateErrorsToString());
// Invoke the default behavior, which produces a ValidationProblemDetails response.
// To produce a custom response, return a different implementation of IActionResult instead.
return builtInFactory(context);
};
});
public static String ModelStateErrorsToString(this ModelStateDictionary modelState)
{
IEnumerable<ModelError> allErrors = modelState.Values.SelectMany(v => v.Errors);
StringBuilder sb = new StringBuilder();
foreach (ModelError error in allErrors)
{
sb.AppendLine($"error {error.ErrorMessage} {error.Exception}");
}
return sb.ToString();
}
As the attribute filter in the life cycle of the .Net Core you can’t handle it. The filter layer with ModelState will run after the model binding.
You can handle it with .Net Core middleware as the following https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1&tabs=aspnetcore2x
If you want to SuppressModelStateInvalidFilter on individual action, consider to use custom attribute suggested on https://learn.microsoft.com/en-us/answers/questions/297568/how-to-suppress-suppressmodelstateinvalidfilter-at.html. (And similar answer https://github.com/aspnet/Mvc/issues/8575)
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
private const string FilterTypeName = "ModelStateInvalidFilterFactory";
public void Apply(ActionModel action)
{
for (var i = 0; i < action.Filters.Count; i++)
{
//if (action.Filters[i] is ModelStateInvalidFilter)
if (action.Filters[i].GetType().Name == FilterTypeName)
{
action.Filters.RemoveAt(i);
break;
}
}
}
}
Example of use
[ApiController]
public class PersonController
{
[SuppressModelStateInvalidFilter]
public ActionResult<Person> Get() => new Person();
}