Customize automatic response on validation error - c#

With asp.net core 2.1 an ApiController will automatically respond with a 400 BadRequest when validation errors occur.
How can I change/modify the response (json-body) that is sent back to the client? Is there some kind of middleware?
I´m using FluentValidation to validate the parameters sent to my controller, but I am not happy with the response that I am get. It looks like
{
"Url": [
"'Url' must not be empty.",
"'Url' should not be empty."
]
}
I want to change the response, cause we have some default values that we attach to responses. So it should look like
{
"code": 400,
"request_id": "dfdfddf",
"messages": [
"'Url' must not be empty.",
"'Url' should not be empty."
]
}

The ApiBehaviorOptions class allows for the generation of ModelState responses to be customised via its InvalidModelStateResponseFactory property, which is of type Func<ActionContext, IActionResult>.
Here's an example implementation:
apiBehaviorOptions.InvalidModelStateResponseFactory = actionContext => {
return new BadRequestObjectResult(new {
Code = 400,
Request_Id = "dfdfddf",
Messages = actionContext.ModelState.Values.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage)
});
};
The incoming ActionContext instance provides both ModelState and HttpContext properties for the active request, which contains everything I expect you could need. I'm not sure where your request_id value is coming from, so I've left that as your static example.
To use this implementation, configure the ApiBehaviorOptions instance in ConfigureServices:
serviceCollection.Configure<ApiBehaviorOptions>(apiBehaviorOptions =>
apiBehaviorOptions.InvalidModelStateResponseFactory = ...
);

Consider creating of custom action filer, e.g.:
public class CustomValidationResponseActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = new List<string>();
foreach (var modelState in context.ModelState.Values)
{
foreach (var error in modelState.Errors)
{
errors.Add(error.ErrorMessage);
}
}
var responseObj = new
{
code = 400,
request_id = "dfdfddf",
messages = errors
};
context.Result = new JsonResult(responseObj)
{
StatusCode = 400
};
}
}
public void OnActionExecuted(ActionExecutedContext context)
{ }
}
You can register it in ConfigureServices:
services.AddMvc(options =>
{
options.Filters.Add(new CustomValidationResponseActionFilter());
});

Related

Swagger unexpected API PATCH action documentation of JsonPatchDocument in example request body

I'm making a Core 3.1 web API and using JsonPatch to create a PATCH action. I have an action named Patch which has a JsonPatchDocument parameter. Here is the action's signature:
[HttpPatch("{id}")]
public ActionResult<FileRecordDto> Patch(int id, [FromBody] JsonPatchDocument<FileRecordQueryParams> patchDoc)
As I understand, the parameter needs to receive JSON data in the following structure, which I've successfully tested with the action:
[
{
"op": "operationName",
"path": "/propertyName",
"value": "newPropertyValue"
}
]
However, the action's documentation generated by Swagger has a different structure:
I'm not familiar with this structure and even "value" property is missing from it, which a JsonPatchDocument object has. Every example of patching with the replace operation I've seen has had the first structure.
Why is Swagger generating an alternate structure for a JsonPatchDocument object in the request body for the PATCH endpoint? How do I fix this?
The NuGet package installed for Swagger:
Swashbuckle.AspNetCore doesn't work propertly with this type JsonPatchDocument<UpdateModel>, which doesn’t represent the expected patch request doument.
You need to custome a document filter to modify the generated specification.
public class JsonPatchDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var schemas = swaggerDoc.Components.Schemas.ToList();
foreach (var item in schemas)
{
if (item.Key.StartsWith("Operation") || item.Key.StartsWith("JsonPatchDocument"))
swaggerDoc.Components.Schemas.Remove(item.Key);
}
swaggerDoc.Components.Schemas.Add("Operation", new OpenApiSchema
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
{"op", new OpenApiSchema{ Type = "string" } },
{"value", new OpenApiSchema{ Type = "string"} },
{"path", new OpenApiSchema{ Type = "string" } }
}
});
swaggerDoc.Components.Schemas.Add("JsonPatchDocument", new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Operation" }
},
Description = "Array of operations to perform"
});
foreach (var path in swaggerDoc.Paths.SelectMany(p => p.Value.Operations)
.Where(p => p.Key == Microsoft.OpenApi.Models.OperationType.Patch))
{
foreach (var item in path.Value.RequestBody.Content.Where(c => c.Key != "application/json-patch+json"))
path.Value.RequestBody.Content.Remove(item.Key);
var response = path.Value.RequestBody.Content.Single(c => c.Key == "application/json-patch+json");
response.Value.Schema = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "JsonPatchDocument" }
};
}
}
}
Register the filter:
services.AddSwaggerGen(c => c.DocumentFilter<JsonPatchDocumentFilter>());
Result:

FluentValidation use custom IActionFilter

I have a custom IActionFilter which I register with my application like so:
services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter()));
The class looks like this:
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
{
public int Order { get; set; } = int.MaxValue - 10;
public void OnActionExecuting(ActionExecutingContext context)
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception == null) return;
var attempt = Attempt<string>.Fail(context.Exception);
if (context.Exception is AttemptException exception)
{
context.Result = new ObjectResult(attempt)
{
StatusCode = exception.StatusCode,
};
}
else
{
context.Result = new ObjectResult(attempt)
{
StatusCode = (int)HttpStatusCode.InternalServerError,
};
}
context.ExceptionHandled = true;
}
}
I would expect that when validating it would invoke the OnActionExecuting method. So I added this code:
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
And I put a breakpoint at the start of the method, but when I run my application and try to post an invalid model, I get this response:
{
"errors": {
"Url": [
"'Url' is invalid. It should start with 'https://www.youtube.com/embed'",
"'Url' is invalid. It should have the correct parameter '?start='"
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|87e96062-42181357ba1ef8c5."
}
How can I force FluentValidation to use my filter?
When [ApiController] attribute is applied ,ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body :
Automatic HTTP 400 responses
To disable the automatic 400 behavior, set the SuppressModelStateInvalidFilter property to true :
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
The best solution I found was:
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var messages = context.ModelState.Values
.Where(x => x.ValidationState == ModelValidationState.Invalid)
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage)
.ToList();
return new BadRequestObjectResult(
Attempt<string>.Fail(
new AttemptException(string.Join($"{Environment.NewLine}", messages))));
};
})

Why is my GlobalExceptionFilter not working on NET.Core?

I'm trying to implement GlobalExceptionFilter in NET Core WEB API.
This my filter code:
public class GlobalExceptionFilter : IExceptionFilter, IDisposable
{
private readonly ILogger _logger;
public GlobalExceptionFilter(ILoggerFactory logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
this._logger = logger.CreateLogger("Global Exception Filter");
}
public void Dispose()
{
}
public void OnException(ExceptionContext context)
{
Dictionary<string, string> data = new Dictionary<string, string>();
HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
String message = String.Empty;
var ex = context.Exception;
TypeSwitch.Do(ex,
TypeSwitch.Case<ArgumentException>(() => { statusCode = HttpStatusCode.BadRequest; }),
TypeSwitch.Case<ArgumentNullException>(() => { statusCode = HttpStatusCode.BadRequest; }),
TypeSwitch.Case<ArgumentOutOfRangeException>(() => { statusCode = HttpStatusCode.BadRequest; }),
TypeSwitch.Case<KeyNotFoundException>(() => { statusCode = HttpStatusCode.NotFound; }),
TypeSwitch.Case<DivideByZeroException>(() => { statusCode = HttpStatusCode.MethodNotAllowed; }),
TypeSwitch.Case<QueryFormatException>(() => { statusCode = HttpStatusCode.MethodNotAllowed; })
);
HttpResponse response = context.HttpContext.Response;
response.StatusCode = (int)statusCode;
response.ContentType = "application/json";
var err = new ErrorPayload()
{
Data = data,
StackTrace = ex.StackTrace,
Message = ex.Message,
StatusCode = (int)statusCode
};
response.WriteAsync(JsonConvert.SerializeObject(err));
}
}
This is my initializing code in
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc( config =>
{
config.Filters.Add(typeof(GlobalExceptionFilter));
}
);
}
And i'm testing the error handling in this controller method
[HttpGet("{idCliente}")]
public IActionResult GetCliente(int idCliente)
{
throw new QueryFormatException("My Custom Exception");
}
Any ideas? thanks!
UPDATE
Well, I have to admit that I asume that wasn't working because Postman shows me connection error instead of MethodNotAllowed of NotFound (404). As suggested i examine the debug and the status response and was actually expected value.
As the docs say (last section)
Prefer middleware for exception handling. Use exception filters only
where you need to do error handling differently based on which MVC
action was chosen. For example, your app might have action methods for
both API endpoints and for views/HTML. The API endpoints could return
error information as JSON, while the view-based actions could return
an error page as HTML.
In your case, if the application only serving API:s then use the exception middleware implementation instead. Here's a good example of one

Model state error filter

On my DTO objects I have several attributes to check it's validity
And I catch such a body response when validation is failed
{
"TransactionId": [
"Max length is 20"
],
"AdditionalInfo": [
"Additional Info has to be no longer than 30 chars"
]
}
But I need to unify all the errors to be with "Error" key.
Something like that
{
"Error": [
"Max length is 20",
"Additional Info has to be no longer than 30 chars"
]
}
I wrote special filter and registered it in Startup.cs
public class ModelStateErrorHandlingFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
context.ModelState.SetModelValue("Errors", new ValueProviderResult(new StringValues(context.ModelState.ToString())));
context.Result = new BadRequestObjectResult(context.ModelState);
}
else
{
await next().ConfigureAwait(false);
}
}
}
But nothing changes. I also have tried to change the key, but it has privat setter
You would need to provide you own custom IActionResult or build the desired object model and pass it to an appropriate ObjectResult.
public class ModelStateErrorHandlingFilter : IAsyncActionFilter {
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
if (!context.ModelState.IsValid) {
var model = new {
Error = context.ModelState
.SelectMany(keyValuePair => keyValuePair.Value.Errors)
.Select(modelError => modelError.ErrorMessage)
.ToArray()
};
context.Result = new BadRequestObjectResult (model);
} else {
await next().ConfigureAwait(false);
}
}
}
setting context.Result will short-circuit the request and pass it your custom response with desired content.

Handle ModelState Validation in ASP.NET Web API

I was wondering how I can achieve model validation with ASP.NET Web API. I have my model like so:
public class Enquiry
{
[Key]
public int EnquiryId { get; set; }
[Required]
public DateTime EnquiryDate { get; set; }
[Required]
public string CustomerAccountNumber { get; set; }
[Required]
public string ContactName { get; set; }
}
I then have a Post action in my API Controller:
public void Post(Enquiry enquiry)
{
enquiry.EnquiryDate = DateTime.Now;
context.DaybookEnquiries.Add(enquiry);
context.SaveChanges();
}
How do I add if(ModelState.IsValid) and then handle the error message to pass down to the user?
For separation of concern, I would suggest you use action filter for model validation, so you don't need to care much how to do validation in your api controller:
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace System.Web.Http.Filters
{
public class ValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
}
Maybe not what you were looking for, but perhaps nice for someone to know:
If you are using .net Web Api 2 you could just do the following:
if (!ModelState.IsValid)
return BadRequest();
Depending on the model errors, you get this result:
{
Message: "The request is invalid."
ModelState: {
model.PropertyA: [
"The PropertyA field is required."
],
model.PropertyB: [
"The PropertyB field is required."
]
}
}
Like this, for example:
public HttpResponseMessage Post(Person person)
{
if (ModelState.IsValid)
{
PersonDB.Add(person);
return Request.CreateResponse(HttpStatusCode.Created, person);
}
else
{
// the code below should probably be refactored into a GetModelErrors
// method on your BaseApiController or something like that
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
return Request.CreateResponse(HttpStatusCode.Forbidden, errors);
}
}
This will return a response like this (assuming JSON, but same basic principle for XML):
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
(some headers removed here)
["A value is required.","The field First is required.","Some custom errorm essage."]
You can of course construct your error object/list any way you like, for example adding field names, field id's etc.
Even if it's a "one way" Ajax call like a POST of a new entity, you should still return something to the caller - something that indicates whether or not the request was successful. Imagine a site where your user will add some info about themselves via an AJAX POST request. What if the information they have tried to entered isn't valid - how will they know if their Save action was successful or not?
The best way to do this is using Good Old HTTP Status Codes like 200 OK and so on. That way your JavaScript can properly handle failures using the correct callbacks (error, success etc).
Here's a nice tutorial on a more advanced version of this method, using an ActionFilter and jQuery: http://asp.net/web-api/videos/getting-started/custom-validation
Or, if you are looking for simple collection of errors for your apps.. here is my implementation of this:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
var errors = new List<string>();
foreach (var state in modelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
var response = new { errors = errors };
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType);
}
}
Error Message Response will look like:
{
"errors": [
"Please enter a valid phone number (7+ more digits)",
"Please enter a valid e-mail address"
]
}
You can use attributes from the System.ComponentModel.DataAnnotations namespace to set validation rules. Refer Model Validation - By Mike Wasson for details.
Also refer video ASP.NET Web API, Part 5: Custom Validation - Jon Galloway
Other References
Take a Walk on the Client Side with WebAPI and WebForms
How ASP.NET Web API binds HTTP messages to domain models, and how to work with media formats in Web API.
Dominick Baier - Securing ASP.NET Web APIs
Hooking AngularJS validation to ASP.NET Web API Validation
Displaying ModelState Errors with AngularJS in ASP.NET MVC
How to render errors to client? AngularJS/WebApi ModelState
Dependency-Injected Validation in Web API
Add below code in startup.cs file
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => new ErrorModel()
{
ErrorCode = ((int)HttpStatusCode.BadRequest).ToString(CultureInfo.CurrentCulture),
ErrorMessage = p.ErrorMessage,
ServerErrorMessage = string.Empty
})).ToList();
var result = new BaseResponse
{
Error = errors,
ResponseCode = (int)HttpStatusCode.BadRequest,
ResponseMessage = ResponseMessageConstants.VALIDATIONFAIL,
};
return new BadRequestObjectResult(result);
};
});
C#
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
...
[ValidateModel]
public HttpResponseMessage Post([FromBody]AnyModel model)
{
Javascript
$.ajax({
type: "POST",
url: "/api/xxxxx",
async: 'false',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
error: function (xhr, status, err) {
if (xhr.status == 400) {
DisplayModelStateErrors(xhr.responseJSON.ModelState);
}
},
....
function DisplayModelStateErrors(modelState) {
var message = "";
var propStrings = Object.keys(modelState);
$.each(propStrings, function (i, propString) {
var propErrors = modelState[propString];
$.each(propErrors, function (j, propError) {
message += propError;
});
message += "\n";
});
alert(message);
};
Here you can check to show the model state error one by one
public HttpResponseMessage CertificateUpload(employeeModel emp)
{
if (!ModelState.IsValid)
{
string errordetails = "";
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
string p = error.ErrorMessage;
errordetails = errordetails + error.ErrorMessage;
}
}
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("error", errordetails);
return Request.CreateResponse(HttpStatusCode.BadRequest, dict);
}
else
{
//do something
}
}
}
I had an issue implementing the accepted solution pattern where my ModelStateFilter would always return false (and subsequently a 400) for actionContext.ModelState.IsValid for certain model objects:
public class ModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest};
}
}
}
I only accept JSON, so I implemented a custom model binder class:
public class AddressModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
var posted = actionContext.Request.Content.ReadAsStringAsync().Result;
AddressDTO address = JsonConvert.DeserializeObject<AddressDTO>(posted);
if (address != null)
{
// moar val here
bindingContext.Model = address;
return true;
}
return false;
}
}
Which I register directly after my model via
config.BindParameter(typeof(AddressDTO), new AddressModelBinder());
You can also throw exceptions as documented here:
http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx
Note, to do what that article suggests, remember to include System.Net.Http
Put this in the startup.cs file
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p =>p.ErrorMessage)).ToList();
var result = new Response
{
Succeeded = false,
ResponseMessage = string.Join(", ",errors)
};
return new BadRequestObjectResult(result);
};
});

Categories