'How to make "One or more validation errors occurred" raise an exception?
I'm running a WebAPI on Core 3.1 and one of my endpoints excepts JSON with a model that has fields with [Required]
attribute like so:
public class Vendor
{
public int ID { get; set; }
[Required(ErrorMessage = "UID is required")] <<<------ required attribute
public string UID { get; set; }
}
When i call this endpoint without setting UID I get the following output as expected:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7ced8b82-4aa34d65daa99a12.",
"errors": {
"Vendor.UID": [
"UID is required"
]
}
}
Altho this output is pretty informative and clear it is not consistent with other error outputs that my API produces by means of ExceptionFilter. Is there any way this errors can be routed to the exception filter as well?
Here is my Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
Common.Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddMvc().AddXmlDataContractSerializerFormatters();
services.AddMvc().AddMvcOptions(options =>
{
options.EnableEndpointRouting = false;
});
services.AddMvcCore(options => options.OutputFormatters.Add(new XmlSerializerOutputFormatter()));
services.AddOptions();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
//Middleware for exception filtering
app.UseMiddleware<ErrorHandlingMiddleware>(new ErrorHandlingMiddlewareOptions { logger = logger });
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseMvc(routes =>
{
routes.MapRoute("EndpointNotFound", "{*url}", new { controller = "Error", action = "EndpointNotFound" });
});
}
}
Solution 1:[1]
You can add filter in your mvc service or controller service
this filter return badrequest
services.AddControllers(option =>
{
option.Filters.Add<ValidationFilter>();
});
to create your filter you can add this class also you can customize this filter to what ever you want
public class ValidationFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//before contrller
if(!context.ModelState.IsValid)
{
var errorsInModelState = context.ModelState
.Where(x => x.Value.Errors.Count > 0)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Errors.Select(x => x.ErrorMessage).ToArray());
var errorResponse = new ErrorResponse();
foreach (var error in errorsInModelState)
{
foreach (var subError in error.Value)
{
var errorModel = new ErrorModel
{
FieldName = error.Key,
Message = subError
};
errorResponse.Error.Add(errorModel);
}
context.Result = new BadRequestObjectResult(errorResponse);
return;
}
await next();
//after controller
}
}
}
I have created error model just like this
public class ErrorModel
{
public string FieldName { get; set; }
public string Message { get; set; }
}
and error response like below
public class ErrorResponse
{
public List<ErrorModel> Error { get; set; } = new List<ErrorModel>();
public bool Successful { get; set; }
}
Solution 2:[2]
In order to achive this functionality you need to implement your own model validator described in this question: Model validation in Web API - Exception is thrown with out a throw statement?
Solution 3:[3]
You might want to take a look at the great library FluentValidation!
Sample:
Build a validator module binding your DTO and create a set of rules.
public class CustomerValidator : AbstractValidator<Customer> {
public CustomerValidator() {
RuleFor(x => x.Surname).NotEmpty();
RuleFor(x => x.Forename).NotEmpty().WithMessage("Please specify a first name");
RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount);
RuleFor(x => x.Address).Length(20, 250);
RuleFor(x => x.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
}
private bool BeAValidPostcode(string postcode) {
// custom postcode validating logic goes here
}
}
Inject it in your controllers through DI:
services.AddControllers()
.AddFluentValidation(s =>
{
s.ValidatorOptions.CascadeMode = CascadeMode.Stop;
s.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
s.ValidatorOptions.LanguageManager.Culture = new CultureInfo("en-US");
s.RegisterValidatorsFromAssemblyContaining<Customer>();
...
// more validators
});
- That way your code looks well organized;
- You get rid of Data Annotations spread all over your code.
- Personalize error messages and validations.
You also may want to check why implementing ControllerBase on your controllers might be the way to go while using web APIs.
Solution 4:[4]
You want to output the JSON like this:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7ced8b82-4aa34d65daa99a12.",
"errors": {
"Vendor.UID": [
"UID is required"
]
}
}
the
type
and thestatus
field, are from here https://github.com/dotnet/aspnetcore/blob/v3.1.17/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs#L54-L108, theClientErrorMapping
will be configured when the dotnet core project setup.the JSON was a
ValidationProblemDetails
which created by theDefaultProblemDetailsFactory
, https://github.com/dotnet/aspnetcore/blob/v3.1.17/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs#L45we can use this
ProblemDetailsFactory
to create the ValidationProblemDetails https://github.com/dotnet/aspnetcore/blob/v3.1.17/src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs#L261
Solution 5:[5]
I have just change in http methos and work fine for the same issue
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]AuthenticateModel model)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | mohammadmahdi Talachi |
Solution 2 | Sándor Jankovics |
Solution 3 | basquiatraphaeu |
Solution 4 | Dharman |
Solution 5 | mxmissile |