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

Posted

技术标签:

【中文标题】在 ASP.NET Web API 中处理 ModelState 验证【英文标题】:Handle ModelState Validation in ASP.NET Web API 【发布时间】:2012-07-26 01:35:27 【问题描述】:

我想知道如何使用 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),然后处理错误信息传递给用户?

【问题讨论】:

【参考方案1】:

为了关注点分离,我建议你使用动作过滤器进行模型验证,所以你不需要太关心如何在你的 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);
        
    

【讨论】:

为此需要的命名空间是System.Net.HttpSystem.NetSystem.Web.Http.ControllersSystem.Web.Http.Filters 在 ASP.NET Web Api 官方页面也有类似的实现:asp.net/web-api/overview/formats-and-model-binding/… 即使不将[ValidationActionFilter]放在web api之上,它仍然会调用代码并给我错误的请求。 值得指出的是,返回的错误响应是由IncludeErrorDetailPolicy控制的。默认情况下,对远程请求的响应仅包含一般的“发生错误”消息,但将其设置为 IncludeErrorDetailPolicy.Always 将包含详细信息(有将详细信息暴露给用户的风险) 您不建议使用 IAsyncActionFilter 是否有具体原因?【参考方案2】:

也许不是您想要的,但也许有人知道是件好事:

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

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

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


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

【讨论】:

当我问这个问题时,请记住 Web API 1 刚刚发布,从那时起它可能已经发生了很多变化:) 确保将属性标记为可选,否则您将得到一个无用的通用“发生错误”。错误信息。 有没有办法更改消息?【参考方案3】:

像这样,例如:

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 等。

即使它是一个“单向”Ajax 调用,例如一个新实体的 POST,您仍然应该向调用者返回一些东西——指示请求是否成功的东西。想象一个网站,您的用户将通过 AJAX POST 请求添加一些关于他们自己的信息。如果他们尝试输入的信息无效怎么办 - 他们如何知道他们的保存操作是否成功?

最好的方法是使用Good Old HTTP Status Codes,比如200 OK等等。这样,您的 javascript 就可以使用正确的回调(错误、成功等)正确处理故障。

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

【讨论】:

这只是返回我的enquiry 对象,但它并没有说明哪些属性无效?因此,如果我将 CustomerAccountNumber 留空,它应该会显示默认验证消息(CusomterAccountNumber 字段是必需的..) 我明白了,那么这是处理模型验证的“正确”方式吗?对我来说似乎有点乱.. 还有其他方法可以做到这一点,比如连接 jQuery 验证。这是一个不错的 Microsoft 示例:asp.net/web-api/videos/getting-started/custom-validation 这个方法和被选为答案“应该”的方法在功能上是相同的,所以这个答案的附加价值是告诉你如何在没有动作过滤器的情况下自己做。 我必须将 errors.Add(error.ErrorMessage); 行更改为 errors.Add(error.Exception.Message); 才能让它为我工作。【参考方案4】:

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

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】:

您可以使用System.ComponentModel.DataAnnotations 命名空间中的属性来设置验证规则。详情请参考Model Validation - By Mike Wasson。

另请参考视频ASP.NET Web API, Part 5: Custom Validation - Jon Galloway

其他参考资料

    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

【讨论】:

【参考方案6】:

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

【讨论】:

确保控制器被标记为属性 [ApiController] 以便如上处理【参考方案7】:

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

【讨论】:

【参考方案8】:

这里可以勾选以一一显示模型状态错误

 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
        
        

【讨论】:

【参考方案9】:

我在实现accepted solution pattern 时遇到问题,我的ModelStateFilter 对于某些模型对象的actionContext.ModelState.IsValid 总是返回false(随后返回400):

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

【讨论】:

【参考方案10】:

您还可以抛出异常,如下所述: http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx

注意,按照文章的建议,请记住包含 System.Net.Http

【讨论】:

【参考方案11】:

把这个放到startup.cs文件中

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

【讨论】:

以上是关于在 ASP.NET Web API 中处理 ModelState 验证的主要内容,如果未能解决你的问题,请参考以下文章

细说Asp.Net Web API消息处理管道

在 ASP.NET Web Api 中捕获所有未处理的异常

使用 NLog 在 ASP.NET Web API 2.1 中进行全局异常处理?

ASP.NET Odata Web API 的错误处理

在 ASP.NET Web API 2 中禁用 *all* 异常处理(为我自己腾出空间)?

正确处理 ASP.net MVC 4 Web Api 路由中的嵌套资源