在ASP.NET Web API中处理ModelState验证


106

我想知道如何使用ASP.NET Web API进行模型验证。我有这样的模型:

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; }
}

然后,我在API控制器中执行了Post操作:

public void Post(Enquiry enquiry)
{
    enquiry.EnquiryDate = DateTime.Now;
    context.DaybookEnquiries.Add(enquiry);
    context.SaveChanges();
}

如何添加if(ModelState.IsValid)然后处理错误消息以传递给用户?

Answers:


186

为了分开关注,我建议您使用动作过滤器进行模型验证,因此您无需过多关心如何在api控制器中进行验证:

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);
        }
    }
}

27
为此,必需的命名空间System.Net.HttpSystem.Net System.Web.Http.ControllersSystem.Web.Http.Filters
克里斯托弗·史蒂文森

11
还有一个类似的实现在官方的ASP.NET Web API页面:asp.net/web-api/overview/formats-and-model-binding/...
埃里克Schierboom

1
即使不将[ValidationActionFilter]放在Web api之上,它仍然会调用代码并给我错误的请求。
micronyks,2015年

1
值得指出的是,返回的错误响应由IncludeErrorDetailPolicy控制。默认情况下,对远程请求的响应仅包含一般的“发生错误”消息,但是将其设置为IncludeErrorDetailPolicy.Always将包含详细信息(可能会向用户暴露详细信息)
Rob

有特定的原因为什么您不建议改用IAsyncActionFilter?
Ravior

30

也许不是您想要的东西,但也许对某人知道很高兴:

如果您使用的是.net Web Api 2,则可以执行以下操作:

if (!ModelState.IsValid)
     return BadRequest(ModelState);

根据模型错误,您将得到以下结果:

{
   Message: "The request is invalid."
   ModelState: {
       model.PropertyA: [
            "The PropertyA field is required."
       ],
       model.PropertyB: [
             "The PropertyB field is required."
       ]
   }
}

1
当我问这个问题时,请紧记一下,Web API 1刚刚发布,此后它可能发生了很多变化:)
CallumVass 2014年

请确保将属性标记为可选,否则您将获得无用的通用“发生错误”。错误信息。
布鲁克(Bouke)2015年

1
有没有办法更改消息?
萨基卜·阿迪尔

28

像这样:

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);
    }
}

这将返回这样的响应(假设为JSON,但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."]

当然,您可以按照自己喜欢的任何方式构造错误对象/列表,例如添加字段名称,字段ID等。

即使是像新实体的POST这样的“单向” Ajax调用,您也应该向调用者返回一些信息,该信息表明请求是否成功。想象一个站点,您的用户将通过AJAX POST请求添加一些有关自己的信息。如果他们尝试输入的信息无效,该怎么办-他们如何知道自己的“保存”操作是否成功?

最好的方法是使用良好的旧HTTP状态代码,例如200 OK。这样,您的JavaScript可以使用正确的回调(错误,成功等)正确处理失败。

这是有关使用ActionFilter和jQuery的此方法的更高级版本的不错的教程:http : //asp.net/web-api/videos/getting-started/custom-validation


那只是返回我的enquiry对象,虽然没有说哪些属性无效?因此,如果我CustomerAccountNumber留空,它应该说默认验证消息(CusomterAccountNumber字段为必填。)
CallumVass

我知道,这是处理模型验证的“正确”方法吗?似乎有点凌乱我..
CallumVass

还有其他方法可以做到这一点,例如与jQuery验证挂钩。这是一个很好的Microsoft示例:asp.net/web-api/videos/getting-started/custom-validation
Anders Arpi

此方法与被选为答案的方法在功能上应该相同,因此,此答案的附加价值是向您展示如何在没有动作过滤器的情况下自己实现。
肖恩·威尔逊

我不得不改变生产线errors.Add(error.ErrorMessage);errors.Add(error.Exception.Message);以使它为我工作。
卡托

9

8

或者,如果您正在寻找应用程序错误的简单集合。.这是我的实现:

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);
        }
    }

错误消息响应如下所示:

{
  "errors": [
    "Please enter a valid phone number (7+ more digits)",
    "Please enter a valid e-mail address"
  ]
}

5

在startup.cs文件中添加以下代码

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);
                };
           });

3

在这里您可以检查以显示模型状态错误

 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
        }
        }

}


3

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)
    {

Java脚本

$.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);
};

2

我在实现可接受的解决方案模式时遇到问题,对于某些模型对象,我ModelStateFilter将始终返回false(随后返回400)actionContext.ModelState.IsValid

public class ModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest};
        }
    }
}

我只接受JSON,因此实现了一个自定义模型绑定程序类:

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;
    }
}

我通过模型直接注册后

config.BindParameter(typeof(AddressDTO), new AddressModelBinder());

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.