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)사용자에게 전달할 오류 메시지를 추가 하고 처리하려면 어떻게해야 합니까?

답변:


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.Http, System.Net System.Web.Http.Controllers하고 System.Web.Http.Filters.
Christopher Stevenson

11
공식 ASP.NET 웹 API를 페이지에서 유사한 구현이있다 : asp.net/web-api/overview/formats-and-model-binding/...은
에릭 Schierboom

1
웹 API 위에 [ValidationActionFilter]를 넣지 않아도 여전히 코드를 호출하고 나쁜 요청을 보냅니다.
micronyks

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."
       ]
   }
}


속성을 선택 사항으로 표시해야합니다. 그렇지 않으면 도움이되지 않는 일반 "오류가 발생했습니다."가 표시됩니다. 에러 메시지.
Bouke

1
메시지를 변경하는 방법이 있습니까?
saquib adil

29

예를 들면 다음과 같습니다.

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 유효성 검사와 연결하는 것과 같은 다른 방법도 있습니다. 여기에 좋은 마이크로 소프트의 예는 다음과 같습니다 asp.net/web-api/videos/getting-started/custom-validation은
앤더스 Arpi

이 방법과 대답으로 선정 된 방법은 기능적으로 동일하므로이 대답은 작업 필터없이 직접 수행 할 수있는 방법을 보여주는 부가 가치가 있습니다.
Shaun Wilson

이 작업을 수행 하려면 줄 errors.Add(error.ErrorMessage);을 로 변경해야했습니다 errors.Add(error.Exception.Message);.
Caltor 2017-04-26

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

씨#

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

자바 스크립트

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.