[C#] ASP.NET Core FluentValidation.AspNetCore .NET 사용법

참조


목적

  • ASP.NET Core 프로젝트를 생성 후, .NET 6 버전에서 FluentValidation.AspNetCore 사용 방법에 대하여 내용 정리합니다.

ASP.NET Core Web API 프로젝트 생성

  • 먼저 ASP.NET Core Web API 프로젝트를 하나 생성합니다.


FluentValidation.AspNetCore 패키지 설치

  • 다음으로 NuGet Package 에서 FluentValidation.AspNetCore 를 입력하여 해당 NuGet Package를 설치 진행합니다.
  • 만약, Package Manager 에서 CLI 명령어로 설치를 할 경우에는 다음 명령어를 입력하시면 됩니다.
> Install-Package FluentValidation.AspNetCore


FluentValidation 구성

  • 설치가 완료 되었으면, Program.cs 에 아래와 같이 FluentValidation.AspNetCore 관련 코드를 추가해 줍니다.
using FluentValidation.AspNetCore;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers()
                .AddFluentValidation(options =>
                {
                    // Validate child properties and root collection elements
                    options.ImplicitlyValidateChildProperties = true; // 옵션 1
                    options.ImplicitlyValidateRootCollectionElements = true; // 옵션 2

                    // Automatic registration of validators in assembly
                    options.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()); //옵션 3
                });

...생략
  • 위 코드는 컨트롤러의 요청 파이프라인에 FluentValidation 을 추가한 것입니다. 아래는 설명입니다.
    • 옵션 1 : 자식 속성의 유효성을 검사할 수 있습니다. 일치하는 유효성 검사기를 찾을 수 있는 경우 하위 속성 여부를 사용하도록 설정하는 옵션은 암시적으로 유효성을 검사해야 합니다. 기본적으로 false 로 설정되어 있으므로 원하는 경우 이 옵션을 활성화해야 합니다.
    • 옵션 2 : 루트 요소의 유효성 검사가 암시적으로 유효성이 검사되어야 합니다. 이것은 루트 모델이 컬렉션이고 요소 유형에 대해 일치하는 유효성 검사기를 찾을 수 있는 경우에만 발생합니다.
    • 옵션 3 : 어셈블리에서 유효성 검사기를 자동으로 등록합니다. System.Reflection 을 사용하여 실행 어셈블리를 얻습니다.

모델에 유효성 검사 추가하기

  • Models 라는 폴더와 Validation 이라는 폴더를 추가하였습니다.
  • 모델 내부에 두 개의 새로운 클래스 Customer.csAddress.cs 를 추가하여 애플리케이션에서 들어오고 나가는 데이터에 대한 모델을 지정했습니다.
  • 아래는 각 모델과 해당 요소를 나타낸 것입니다.

Customer.cs

namespace FluentValidationSample.Models
{
    public class Customer
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }    
        public int Phone { get; set; }
        public Address Address { get; set; }
    }
}

Address.cs

namespace FluentValidationSample.Models
{
    public class Address
    {
        public int Id { get; set; }
        public string Street1 { get; set; }
        public string Street2 { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
        public string City { get; set; }
        public string PostalCode { get; set; }
    }
}
  • 다음으로 Validation 폴더 안에 두개의 클래스를 추가합니다.
    • CustomerValidator.cs
    • AddressValidator.cs
  • 위 2개의 클래스 내용은 다음과 같습니다.

CustomerValidator.cs

using FluentValidation;
using FluentValidationSample.Models;

namespace FluentValidationSample.Validation
{
    public class CustomerValidator : AbstractValidator<Customer>
    {
        public CustomerValidator()
        {
            // Check name is not null, empty and is between 1 and 250 characters
            RuleFor(customer => customer.FirstName).NotNull().NotEmpty().Length(1, 250);
            RuleFor(customer => customer.LastName).NotNull().NotEmpty().Length(1, 250);

            // Validate Phone with a custom error message
            RuleFor(customer => customer.Phone).NotEmpty().WithMessage("Please add a phone number");

            // Validate Age for submitted customer has to be between 21 and 100 years old.
            RuleFor(customer => customer.Age).InclusiveBetween(21, 100);

            // Validate the address (it's a complex property)
            RuleFor(customer => customer.Address).InjectValidator();
        }
    }
}

AddressValidator.cs

using FluentValidation;
using FluentValidationSample.Models;

namespace FluentValidationSample.Validation
{
    public class AddressValidator : AbstractValidator<Address>
    {
        public AddressValidator()
        {
            // Validate address is not longer than 60 chars as many APIs for carriers doesn't allow
            // longer address as they cannot be at the shipment label
            RuleFor(address => address.Street1).NotNull().NotEmpty().Length(1, 60);
            RuleFor(address => address.Street2).Length(1, 60);

            // Validate Country
            RuleFor(address => address.Country).NotNull().NotEmpty().WithMessage("Please add the destination country");

            // Validate City and ZIP
            RuleFor(address => address.PostalCode).NotNull().NotEmpty().WithMessage("Please add reciever postcode");
            RuleFor(address => address.PostalCode).Must(ValidPostCode).WithMessage("Postalcode is not valid");
            RuleFor(address => address.City).NotNull().NotEmpty().WithMessage("Please add the reciever city");
        }

        private bool ValidPostCode(string postalCode)
        {
            // Add logic for validating postalcode here...
            return true;
        }
    }
}
  • 보시다시피 FluentValidation에서 AbstractValidator<T> 를 상속 받았습니다.
  • 여기서 <T> 는 계속 진행하기 전에 유효성을 검사하려는 클래스/모델 입니다.
  • 유효성 검사 규칙은 항상 클래스의 생성자 내부에 정의되어야 합니다.
  • 새 유효성 검사 규칙을 만들 때마다 RuleFor 메서드를 사용하고 유효성을 검사할 속성을 지정하는 람다식을 전달하기만 하면 됩니다.
  • 람다식 후에 내장 유효성 검사기를 사용하거나 직접 생성하여 사용할 수 있습니다.
  • Customer.cs 에서 주소 클래스/모델의 유효성을 검사하기 위해 다른 유효성 검사기를 주입하고 있습니다.
  • List<T> 와 같은 데이터 컬렉션이 T is Address 인 경우, 복합 유형의 유효성 검사가 필요합니다.
  • 다음 코드를 추가하여 그렇게 할 수 있습니다.
RuleForEach(x => x.Addresses).SetValidator(new AddressValidator());
  • 위와 같이 코드를 작성하게 되면, 주소 유효성 검사기를 사용하여 목록의 각 객체에 대해 유효성 검사를 실행하는 새 규칙이 생성됩니다.

API Endpoints 에서 모델 검증하기

  • 이를 쉽게 하기 위해 고객과 관련된 요청을 처리하는 매우 간단한 컨트롤러를 만들었습니다.
  • CustomerController.cs 컨트롤러를 생성하였고, 다음 코드를 추가하였습니다.

    참고로, 컨트롤러에서 FluentValidation을 사용할 때 이전에 등록한 미들웨어가 올바른 유효성 검사기를 자동으로 선택하므로 사용하려는 유효성 검사기를 지정할 필요가 없습니다. 유요성 검사를 수행하기 위해 여전히 ModelState를 사용하고 유효성 검사기가 실패하면 ModelState에서 반환됩니다.

using FluentValidationSample.Models;
using Microsoft.AspNetCore.Mvc;

namespace FluentValidationSample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class CustomerController : ControllerBase
    {
        [HttpPost("add")]
        public IActionResult Add(Customer model)
        {
            if(!ModelState.IsValid)
            {
                return StatusCode(StatusCodes.Status400BadRequest, ModelState);
            }

            return StatusCode(StatusCodes.Status200OK, "Model is valid!");
        }
    }
}
  • 위와 같이 컨트롤러 코드 작성을 완료 하였다면, 테스트 실행을 위해 애플리케이션을 가동하고 일부 샘플 데이터가 포함된 EndPoint 를 요청합니다.
  • 다음은 오류를 생성하는 요청에 대한 몇 가지 샘플 데이터입니다.
{
  "id": 0,
  "firstName": "string",
  "lastName": "string",
  "age": 0,
  "phone": 0,
  "address": {
    "id": 0,
    "street1": "string",
    "street2": "string",
    "state": "string",
    "country": "string",
    "city": "string",
    "postalCode": "string"
  }
}
  • POST 가 위의 데이터로 EndPoint 를 요청할 때 다음과 같은 오류가 발생합니다.

  • RuleFor 메서드에 WithMessage("") 를 입력했기 때문에 이제 모델을 검증할 때 반환된 메시를 받고 유효하지 않은 일부 데이터를 보냈습니다.

  • 위의 이미지에서 볼 수 있듯이 미들웨어는 자동으로 올바른 클래스/모델을 선택합니다.

  • 원하는 경우 유효성 검사기를 만들려는 클래스/모델을 명시적으로 지정할 수도 있습니다.

  • 그런 다음 Validate 메서드는 두 가지 속성이 있는 ValidationResult 객체를 반환합니다.

  • 첫 번째는 IsValid 이며 유효성 검사가 성공했는지 여부를 알려주는 Bool 입니다.

  • 두 번째 속성은 오류이며 오류에 대한 세부 정보와 함께 ValidationFailure 객체 컬렉션을 포함합니다.

  • 코드로는 다음과 같이 구현할 수 있습니다.

using FluentValidationSample.Models;
using FluentValidationSample.Validation;
using Microsoft.AspNetCore.Mvc;

namespace FluentValidationSample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class CustomerController : ControllerBase
    {
        [HttpPost("add")]
        public IActionResult Add(Customer model)
        {
            if(!ModelState.IsValid)
            {
                return StatusCode(StatusCodes.Status400BadRequest, ModelState);
            }

            return StatusCode(StatusCodes.Status200OK, "Model is valid!");
        }

        [HttpPut("update")]
        public IActionResult Update(Customer model)
        {
            CustomerValidator customerValidator = new();

            var validatorResult = customerValidator.Validate(model);

            if(!validatorResult.IsValid)
            {
                return StatusCode(StatusCodes.Status400BadRequest, validatorResult.Errors);
            }

            return StatusCode(StatusCodes.Status200OK, "Model is valid for update!");
        }
    }
}
  • 요청에 오류가 있는 업데이트 경로에 대해 PUT 요청을 하면 오류 유효성 검사 결과가 반환힙니다.

728x90

이 글을 공유하기

댓글

Designed by JB FACTORY