【问题标题】:Handle ModelState Validation in ASP.NET Web API在 ASP.NET Web API 中处理 ModelState 验证
【发布时间】: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),然后处理错误信息传递给用户?

【问题讨论】:

    标签: c# asp.net-web-api


    【解决方案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】:
      【解决方案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);
                          };
                      });
              

              【讨论】:

                猜你喜欢
                • 2019-04-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-01-21
                • 1970-01-01
                相关资源
                最近更新 更多