ASP.NET Web API의 모델 유효성 검사

이 문서에서는 모델에 주석을 달고, 데이터 유효성 검사에 주석을 사용하고, 웹 API에서 유효성 검사 오류를 처리하는 방법을 보여 줍니다. 클라이언트가 웹 API에 데이터를 보낼 때 처리를 수행하기 전에 데이터의 유효성을 검사하려는 경우가 많습니다.

데이터 주석

ASP.NET Web API System.ComponentModel.DataAnnotations 네임스페이스의 특성을 사용하여 모델의 속성에 대한 유효성 검사 규칙을 설정할 수 있습니다. 다음 모델을 살펴보세요.

using System.ComponentModel.DataAnnotations;

namespace MyApi.Models
{
    public class Product
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        public decimal Price { get; set; }
        [Range(0, 999)]
        public double Weight { get; set; }
    }
}

ASP.NET MVC에서 모델 유효성 검사를 사용한 경우 익숙해 보일 것입니다. 필수 특성은 속성이 Name null이 아니어야 한다고 말합니다. Range 특성은 0에서 999 사이여야 한다고 말합니다Weight.

클라이언트가 다음 JSON 표현을 사용하여 POST 요청을 보낸다고 가정합니다.

{ "Id":4, "Price":2.99, "Weight":5 }

클라이언트에 필수로 표시된 속성이 Name 포함되어 있지 않은 것을 볼 수 있습니다. Web API는 JSON Product 을 instance 변환할 때 유효성 검사 특성에 대해 의 유효성을 검사합니다Product. 컨트롤러 작업에서 모델이 유효한지 여부를 검사 수 있습니다.

using MyApi.Models;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace MyApi.Controllers
{
    public class ProductsController : ApiController
    {
        public HttpResponseMessage Post(Product product)
        {
            if (ModelState.IsValid)
            {
                // Do something with the product (not shown).

                return new HttpResponseMessage(HttpStatusCode.OK);
            }
            else
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
        }
    }
}

모델 유효성 검사는 클라이언트 데이터가 안전하다는 것을 보장하지 않습니다. 애플리케이션의 다른 계층에서 추가 유효성 검사가 필요할 수 있습니다. 예를 들어 데이터 계층은 외래 키 제약 조건을 적용할 수 있습니다. Entity Framework에서 Web API 사용 자습서에서는 이러한 문제 중 일부를 살펴봅니다.

"언더 게시": 클라이언트가 일부 속성을 떠날 때 언더 게시가 발생합니다. 예를 들어 클라이언트가 다음을 보낸다고 가정합니다.

{"Id":4, "Name":"Gizmo"}

여기서 클라이언트는 또는 Weight에 대한 Price 값을 지정하지 않았습니다. JSON 포맷터는 누락된 속성에 기본값 0을 할당합니다.

Product Store 점 모델이 있는 코드 조각의 스크린샷은 제품의 드롭다운 메뉴 옵션을 점으로 표시합니다.

0은 이러한 속성에 유효한 값이므로 모델 상태는 유효합니다. 이것이 문제인지 여부는 시나리오에 따라 달라집니다. 예를 들어 업데이트 작업에서 "0"과 "설정 안 함"을 구분할 수 있습니다. 클라이언트가 값을 설정하도록 강제하려면 속성을 null 허용으로 설정하고 필수 특성을 설정합니다.

[Required]
public decimal? Price { get; set; }

"초과 게시": 클라이언트는 예상보다 많은 데이터를 보낼 수도 있습니다. 예:

{"Id":4, "Name":"Gizmo", "Color":"Blue"}

여기서 JSON에는 모델에 없는 속성("Color")이 Product 포함됩니다. 이 경우 JSON 포맷터는 이 값을 무시합니다. (XML 포맷터는 동일한 작업을 수행합니다.) 모델에 읽기 전용으로 사용할 속성이 있는 경우 과도하게 게시하면 문제가 발생합니다. 예:

public class UserProfile
{
    public string Name { get; set; }
    public Uri Blog { get; set; }
    public bool IsAdmin { get; set; }  // uh-oh!
}

사용자가 속성을 업데이트 IsAdmin 하고 관리자로 승격하지 않도록 합니다. 가장 안전한 전략은 클라이언트가 보낼 수 있는 것과 정확히 일치하는 모델 클래스를 사용하는 것입니다.

public class UserProfileDTO
{
    public string Name { get; set; }
    public Uri Blog { get; set; }
    // Leave out "IsAdmin"
}

참고

Brad Wilson의 블로그 게시물 "입력 유효성 검사 및 ASP.NET MVC의 모델 유효성 검사"에는 게시 부족 및 초과 게시에 대한 좋은 논의가 있습니다. 게시물은 ASP.NET MVC 2에 관한 것이지만 문제는 여전히 Web API와 관련이 있습니다.

유효성 검사 오류 처리

유효성 검사에 실패할 때 Web API는 클라이언트에 오류를 자동으로 반환하지 않습니다. 모델 상태를 검사 적절하게 응답하는 것은 컨트롤러 작업에 달려 있습니다.

컨트롤러 작업이 호출되기 전에 모델 상태를 검사 작업 필터를 만들 수도 있습니다. 다음 코드에서는 예제를 보여 줍니다.

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;

namespace MyApi.Filters
{
    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
}

모델 유효성 검사에 실패하면 이 필터는 유효성 검사 오류가 포함된 HTTP 응답을 반환합니다. 이 경우 컨트롤러 작업이 호출되지 않습니다.

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Tue, 16 Jul 2013 21:02:29 GMT
Content-Length: 331

{
  "Message": "The request is invalid.",
  "ModelState": {
    "product": [
      "Required property 'Name' not found in JSON. Path '', line 1, position 17."
    ],
    "product.Name": [
      "The Name field is required."
    ],
    "product.Weight": [
      "The field Weight must be between 0 and 999."
    ]
  }
}

이 필터를 모든 Web API 컨트롤러에 적용하려면 구성 중에 필터의 instance HttpConfiguration.Filters 컬렉션에 추가합니다.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ValidateModelAttribute());

        // ...
    }
}

또 다른 옵션은 필터를 개별 컨트롤러 또는 컨트롤러 작업의 특성으로 설정하는 것입니다.

public class ProductsController : ApiController
{
    [ValidateModel]
    public HttpResponseMessage Post(Product product)
    {
        // ...
    }
}