2

I have a custom IModelBinder for my model:

public class MyBinder : IModelBinder {

public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
        // bla-bla-bla

        bindingContext.ModelState.AddModelError(
            bindingContext.ModelName, "Request value is invalid.");
        return false;
    }
}

I expect that when invalid value is passed in a request HTTP 400 Bad Request is returned automatically. However, this does not happen. What should I do to make Web API return HTTP 400 if there are any binding errors?

UserControl
  • 14,766
  • 20
  • 100
  • 187
  • Possible duplicate of [Handle ModelState Validation in ASP.NET Web API](https://stackoverflow.com/questions/11686690/handle-modelstate-validation-in-asp-net-web-api) – Ruben Bartelink May 26 '17 at 10:40
  • This is a dup of https://stackoverflow.com/questions/11686690/handle-modelstate-validation-in-asp-net-web-api – Ruben Bartelink May 26 '17 at 10:41

2 Answers2

2

Return it in your controller:

if (!ModelState.IsValid)
{
    return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
beautifulcoder
  • 10,832
  • 3
  • 19
  • 29
2

You can do as beautifulcoder suggests but that leaves much to be desired because you need to repeat that on every action. I would suggest your create an ActionFilterAttribute which onActionExecuting and onActionExecuted validates that the modelstate is valid and returns the BadRequest. You can then apply this to separate actions as [BadRequestIfModelNotValid] or to the global filters to have it applied to every requests.

public sealed class BadRequestIfModelNotValidAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
  /// <summary>
  /// if the modelstate is not valid before invoking the action return badrequest
  /// </summary>
  /// <param name="actionContext"></param>
  public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
  {
    var modelState = actionContext.ModelState;
    if (!modelState.IsValid)
      actionContext.Response = generateModelStateBadRequestResponse(modelState, actionContext.Request);

    base.OnActionExecuting(actionContext);//let other filters run if required
  }

  /// <summary>
  /// if the action has done additional modelstate checks which made it invalid we are going to replace the response with a badrequest
  /// </summary>
  /// <param name="actionExecutedContext"></param>
  public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
  {
    var modelState = actionExecutedContext.ActionContext.ModelState;
    if (!modelState.IsValid)
      actionExecutedContext.Response = generateModelStateBadRequestResponse(modelState, actionExecutedContext.Request);

    base.OnActionExecuted(actionExecutedContext);
  }

  private HttpResponseMessage generateModelStateBadRequestResponse(IEnumerable<KeyValuePair<string, ModelState>> modelState, HttpRequestMessage request)
  {
    var errors = modelState
      .Where(s => s.Value.Errors.Count > 0)
      .Select(s => new ApiErrorMessage {
          Parameter = s.Key,
          Message = getErrorMessage(s.Value.Errors.First())
        }) //custom class to normalize error responses from api
        .ToList();

    return request.CreateResponse(System.Net.HttpStatusCode.BadRequest, new ApiError
    {
      ExceptionType = typeof(ArgumentException).FullName,
      Messages = errors
    });
  }

  /// <summary>
  /// retrieve the error message or fallback to exception if possible
  /// </summary>
  /// <param name="modelError"></param>
  /// <returns></returns>
  private static string getErrorMessage(ModelError modelError)
  {
    if(!string.IsNullOrWhiteSpace(modelError.ErrorMessage))
      return modelError.ErrorMessage;

    if(modelError.Exception != null)
      return modelError.Exception.Message;

    return "unspecified error";
  }
}
Mark van Straten
  • 9,287
  • 3
  • 38
  • 57