ASP.NET Core MVC 및 Razor Pages의 모델 유효성 검사

이 문서에서는 ASP.NET Core MVC 또는 Razor Pages 앱에서 사용자 입력의 유효성을 검사하는 방법에 대해 설명합니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법). 다운로드 예제는 영역을 테스트하기 위한 기초적인 앱을 제공합니다.

모델 상태

모델 상태는 모델 바인딩 및 모델 유효성 검사 두 하위 시스템에서 발생하는 오류를 나타냅니다. 모델 바인딩에서 발생하는 오류는 일반적으로 데이터 변환 오류입니다. 예를 들어 정수 필드에 “x”가 입력됩니다. 모델 유효성 검사는 모델 바인딩 후에 발생하며 데이터가 비즈니스 규칙에 맞지 않으면 오류를 보고합니다. 예를 들어 1에서 5 사이의 등급을 필요한 필드에 0이 입력됩니다.

모델 바인딩과 모델 유효성 검사 모두 컨트롤러 작업 또는 Razor Pages 처리기 메서드를 실행하기 전에 실행됩니다. 웹앱의 경우 ModelState.IsValid를 검사하고 적절히 반응하는 기능은 앱에서 실행합니다. 웹앱은 일반적으로 다음 Razor Pages 예제와 같이 오류 메시지로 페이지를 다시 표시합니다.

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

컨트롤러 및 뷰가 있는 ASP.NET Core MVC의 경우 다음 예제에서는 컨트롤러 작업 내부에서 ModelState.IsValid를 확인하는 방법을 보여 줍니다.

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

웹 API 컨트롤러는 [ApiController] 특성을 포함하는 경우 ModelState.IsValid를 확인할 필요가 없습니다. 이 경우 모델 상태가 잘못되면 오류 세부 정보를 포함하는 자동 HTTP 400 응답이 반환됩니다. 자세한 정보는 자동 HTTP 400 응답을 참조하세요.

유효성 검사 다시 실행

유효성 검사는 자동으로 실행 되지만 수동으로 반복하는 것이 좋습니다. 예를 들어 속성에 대한 값을 계산하고 해당 속성을 계산된 값으로 설정한 후 유효성 검사를 다시 실행하는 것이 좋습니다. 유효성 검사를 다시 실행하려면 ModelStateDictionary.ClearValidationState를 호출하여 TryValidateModel에 따라 유효성이 검사되는 모델에 대한 특정 유효성 검사를 제거합니다.

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

유효성 검사 특성

유효성 검사 특성을 사용하여 모델 속성에 대한 유효성 검사 규칙을 지정할 수 있습니다. 샘플 앱의 다음 예제는 유효성 검사 특성으로 주석을 단 모델 클래스를 나타냅니다. [ClassicMovie] 특성은 사용자 지정 유효성 검사 특성이며 다른 특성은 기본 제공 특성입니다. 사용자 지정 특성을 구현하는 다른 방법을 보여 주는 [ClassicMovieWithClientValidator]는 표시되지 않습니다.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

기본 제공 특성

다음은 몇 가지 기본 제공 유효성 검사 특성입니다.

  • [ValidateNever]: 속성 또는 매개 변수가 유효성 검사에서 제외되어야 함을 나타냅니다.
  • [CreditCard]: 속성에 신용 카드 형식이 있는지 유효성을 검사합니다. jQuery 유효성 검사 추가 방법이 필요합니다.
  • [Compare]: 모델의 두 속성이 일치하는지 유효성을 검사합니다.
  • [EmailAddress]: 속성에 전자 메일 형식이 있는지 유효성을 검사합니다.
  • [Phone]: 속성에 전화 번호 형식이 있는지 유효성을 검사합니다.
  • [Range]: 속성 값이 지정된 범위 내에 포함되는지 유효성을 검사합니다.
  • [RegularExpression]: 속성 값이 지정된 정규 식과 일치하는지 유효성을 검사합니다.
  • [Required]: 필드가 null이 아닌지 확인합니다. 이 특성의 동작에 대한 자세한 내용은 [Required] 특성을 참조하세요.
  • [StringLength]: 문자열 속성 값이 지정된 길이 한계를 초과하지 않는지 유효성을 검사합니다.
  • [Url]: 속성에 URL 형식이 있는지 유효성을 검사합니다.
  • [Remote]: 서버에 대한 작업 명령을 호출하여 클라이언트에 대한 입력의 유효성을 검사합니다. 이 특성의 동작에 대한 자세한 내용은 [Remote] 특성을 참조하세요.

네임스페이스에서 유효성 검사 특성의 전체 목록을 찾을 System.ComponentModel.DataAnnotations 수 있습니다.

오류 메시지

유효성 검사 특성을 사용하여 잘못된 입력에 대해 표시할 오류 메시지를 지정할 수 있습니다. 예시:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

내부적으로 이 특성은 필드 이름에 대한 자리 표시자 및 경우에 따라 추가 자리 표시자를 사용하여 String.Format을 호출합니다. 예시:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Name 속성에 적용할 경우 위의 코드에 의해 생성되는 오류 메시지는 "이름의 길이는 6문자에서 8문자 사이여야 합니다"일 것입니다.

특정 특성의 오류 메시지에 대해 String.Format 에 전달되는 매개 변수를 알아보려면 DataAnnotations 소스 코드를 참조하세요.

유효성 검사 오류의 JSON 속성 이름 사용

기본적으로 유효성 검사 오류가 발생하면 모델 유효성 검사는 속성 이름을 오류 키로 사용하여 ModelStateDictionary를 생성합니다. 단일 페이지 앱과 같은 일부 앱은 Web API에서 생성된 유효성 검사 오류에 JSON 속성 이름을 사용할 수 있습니다. 다음 코드는 SystemTextJsonValidationMetadataProvider를 사용하여 JSON 속성 이름을 사용하도록 유효성 검사를 구성합니다.

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

다음 코드는 Json.NET를 사용할 때 NewtonsoftJsonValidationMetadataProvider를 사용하여 JSON 속성 이름을 사용하도록 유효성 검사를 구성합니다.

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

카멜식 대/소문자를 사용하는 정책의 예는 GitHub의 Program.cs를 참조하세요.

Null을 허용하지 않는 참조 형식 및 [Required] 특성

유효성 검사 시스템은 Null을 허용하지 않는 매개 변수 또는 바인딩되는 속성을 [Required(AllowEmptyStrings = true)] 특성을 포함한 것처럼 처리합니다. Nullable 컨텍스트를 사용하도록 설정하면 MVC는 [Required(AllowEmptyStrings = true)] 특성을 사용하는 것처럼 암시적으로 null을 허용하지 않는 속성 또는 매개 변수의 유효성 검사를 시작합니다. 다음 코드를 생각해 봅시다.

public class Person
{
    public string Name { get; set; }
}

앱이 <Nullable>enable</Nullable>로 빌드된 경우 JSON 또는 양식 게시의 Name에 대한 값이 누락되면 유효성 검사 오류가 발생합니다. 특성이 암시적이므로 모순 [Required(AllowEmptyStrings = true)] 된 것처럼 보일 수 있지만 빈 문자열이 기본적으로 null로 변환되기 때문에 이 동작이 필요합니다. Null 허용 참조 형식을 사용하여 Name 속성에 null 또는 누락된 값을 지정할 수 있습니다.

public class Person
{
    public string? Name { get; set; }
}

Program.cs에서 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes를 구성하여 이 동작을 사용하지 않도록 설정할 수 있습니다.

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

서버에 대한 [Required] 유효성 검사

서버에서 속성이 Null인 경우 필수 값이 없는 것으로 간주합니다. Null을 허용하지 않는 필드는 언제나 유효하며 [Required] 특성의 오류 메시지는 표시되지 않습니다.

그러나 Null을 허용하지 않는 속성에 대한 모델 바인딩이 실패하여 The value '' is invalid와 같은 오류 메시지가 표시될 수 있습니다. Null을 허용하지 않는 형식의 서버 쪽 유효성 검사에 대한 사용자 지정 오류 메시지를 지정하려는 경우 다음과 같은 방법이 있습니다.

  • 필드를 Null 허용으로 만듭니다(예: decimal 대신에 decimal?). Null을 허용하는 <T> 값 형식은 표준 Nul을 허용 형식처럼 처리됩니다.

  • 다음 예제에서와 같이 모델 바인딩에 의해 사용할 기본 오류 메시지를 지정합니다.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    기본 메시지를 설정할 수 있는 모델 바인딩 오류에 대한 자세한 내용은 DefaultModelBindingMessageProvider를 참조하십시오.

클라이언트에 대한 [Required] 유효성 검사

클라이언트에서 Null을 허용하지 않는 형식 및 문자열은 서버의 경우와 다르게 처리됩니다. 클라이언트에서:

  • 값은 해당 값에 대한 입력이 있는 경우에만 존재하는 것으로 간주됩니다. 그러므로 클라이언트 쪽 유효성 검사에서는 Null을 허용하지 않는 형식을 Null 허용 형식과 동일하게 처리합니다.
  • 문자열 필드의 공백은 jQuery 유효성 검사 필수 메서드에 의해 유효한 입력으로 간주됩니다. 서버 쪽 유효성 검사에서는 공백만 입력된 경우 필수 문자열 필드를 잘못된 것으로 간주합니다.

위에서 설명했듯이 Null을 허용하지 않는 형식은 [Required(AllowEmptyStrings = true)] 특성을 포함한 것처럼 처리됩니다. 즉, [Required(AllowEmptyStrings = true)] 특성을 적용하지 않더라도 클라이언트 쪽 유효성 검사가 이루어집니다. 그러나 해당 특성을 사용하지 않으면 기본 오류 메시지가 표시됩니다. 사용자 지정 오류 메시지를 지정하려면 해당 특성을 사용하십시오.

[Remote] 특성

[Remote] 특성은 필드 입력이 유효한지 여부를 결정하기 위해 서버에서 메서드를 호출해야 하는 클라이언트 쪽 유효성 검사를 구현합니다. 예를 들어 앱에서 사용자 이름을 이미 사용 중인지 여부를 확인해야 할 수 있습니다.

원격 유효성 검사를 구현하려면:

  1. JavaScript가 호출할 작업 메서드를 만듭니다. jQuery 유효성 검사 remote 메서드는 JSON 응답을 수신해야 합니다.

    • true는 입력 데이터가 유효함을 의미합니다.
    • false, undefined 또는 null은 입력이 잘못되었음을 의미합니다. 기본 오류 메시지를 표시합니다.
    • 다른 문자열은 모두 입력이 잘못되었음을 의미합니다. 문자열을 사용자 지정 오류 메시지로 표시합니다.

    다음은 사용자 지정 오류 메시지를 반환하는 작업 메서드의 예입니다.

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 다음 예제와 같이 모델 클래스에서는 유효성 검사 작업 메서드를 가리키는 [Remote] 특성으로 속성에 주석을 답니다.

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

JavaScript를 사용하지 않도록 설정한 클라이언트에 대해서도 서버 쪽 유효성 검사를 구현해야 합니다.

추가 필드

[Remote] 특성의 AdditionalFields 속성을 사용하여 서버의 데이터를 기준으로 필드 조합의 유효성을 검사할 수 있습니다. 예를 들어 User 모델이 FirstNameLastName 속성을 포함한 경우 해당 이름 쌍을 이미 가진 기존 사용자가 없는지 확인하는 것이 좋습니다. 다음 예제에서는 AdditionalFields을 사용하는 방법을 보여 줍니다.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields는 명시적으로 믄자열 "FirstName" 및 "LastName"으로 설정할 수 있지만 nameof 연산자를 사용하면 나중에 리팩터링이 간단해집니다. 이 유효성 검사의 작업 메서드는 firstNamelastName 인수를 모두 허용해야 합니다.

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

사용자가 이름 또는 성을 입력하면 JavaScript에서는 원격 호출을 실행하여 해당 이름 쌍이 사용 중인지 확인합니다.

두 개 이상의 추가 필드에 대한 유효성을 검사하려면 해당 필드를 쉼표로 구분된 목록으로 제공합니다. 예를 들어 MiddleName 속성을 모델에 추가하려면 [Remote] 특성을 다음 예제와 같이 설정합니다.

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

모든 특성 인수와 같은 AdditionalFields은 상수 식이어야 합니다. 따라서 AdditionalFields를 초기화하기 위해 보간된 문자열을 사용하거나 Join을 호출하지 마십시오.

기본 제공 특성에 대한 대안

기본 제공 특성이 제공하지 않는 유효성 검사가 필요한 경우 다음과 같이 할 수 있습니다.

사용자 지정 특성

기본 제공 유효성 검사 특성이 처리하지 않는 시나리오의 경우 사용자 지정 유효성 검사 특성을 만들 수 있습니다. ValidationAttribute에서 상속하는 클래스를 만들고 IsValid 메서드를 재정의합니다.

IsValid 메서드에 대해서는 유효성을 검사할 입력인 이라는 개체를 지정할 수 있습니다. 또한 오버로드에 대해서는 모델 바인딩에 의해 생성된 모델 인스턴스와 같은 추가 정보를 제공하는 ValidationContext 개체를 지정할 수 있습니다.

다음 예제에서는 클래식 장르의 영화에 대한 개봉 날짜가 지정된 연도보다 이후가 아닌지 유효성을 검사합니다. [ClassicMovie] 특성:

  • 서버에서만 실행됩니다.
  • 클래식 동영상의 경우 릴리스 날짜의 유효성을 검사합니다.
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

위의 예제에서 movie 변수는 양식 제출에서 제공된 데이터를 포함하는 Movie 개체를 나타냅니다. 유효성 검사에 실패하면 오류 메시지가 발생하여 ValidationResult가 반환됩니다.

IValidatableObject

앞의 예제는 Movie 형식에 대해서만 작동합니다. 또 다른 클래스 수준 유효성 검사 방법은 다음 예제와 같이 모델 클래스 내에 IValidatableObject를 구현하는 것입니다.

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

사용자 지정 유효성 검사

다음 코드는 모델을 검사한 후 모델 오류를 추가하는 방법을 보여줍니다.

if (Contact.Name == Contact.ShortName)
{
    ModelState.AddModelError("Contact.ShortName", 
                             "Short name can't be the same as Name.");
}

다음 코드는 컨트롤러에서 유효성 검사 테스트를 구현합니다.

if (contact.Name == contact.ShortName)
{
    ModelState.AddModelError(nameof(contact.ShortName),
                             "Short name can't be the same as Name.");
}

다음 코드는 전화 번호와 전자 메일이 고유한지 확인합니다.

public async Task<IActionResult> OnPostAsync()
{
    // Attach Validation Error Message to the Model on validation failure.          

    if (Contact.Name == Contact.ShortName)
    {
        ModelState.AddModelError("Contact.ShortName", 
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == Contact.PhoneNumber))
    {
        ModelState.AddModelError("Contact.PhoneNumber",
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == Contact.Email))
    {
        ModelState.AddModelError("Contact.Email", "The Email is already in use.");
    }

    if (!ModelState.IsValid || _context.Contact == null || Contact == null)
    {
        // if model is invalid, return the page with the model state errors.
        return Page();
    }
    _context.Contact.Add(Contact);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

다음 코드는 컨트롤러에서 유효성 검사 테스트를 구현합니다.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,ShortName,Email,PhoneNumber")] Contact contact)
{
    // Attach Validation Error Message to the Model on validation failure.
    if (contact.Name == contact.ShortName)
    {
        ModelState.AddModelError(nameof(contact.ShortName),
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == contact.PhoneNumber))
    {
        ModelState.AddModelError(nameof(contact.PhoneNumber),
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == contact.Email))
    {
        ModelState.AddModelError(nameof(contact.Email), "The Email is already in use.");
    }

    if (ModelState.IsValid)
    {
        _context.Add(contact);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(contact);
}

고유한 전화 번호 또는 전자 메일에 대한 확인은 일반적으로 원격 유효성 검사를 통해 수행됩니다.

ValidationResult

다음 사용자 지정을 고려합니다.ValidateNameAttribute

public class ValidateNameAttribute : ValidationAttribute
{
    public ValidateNameAttribute()
    {
        const string defaultErrorMessage = "Error with Name";
        ErrorMessage ??= defaultErrorMessage;
    }

    protected override ValidationResult? IsValid(object? value,
                                         ValidationContext validationContext)
    {
        if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
        {
            return new ValidationResult("Name is required.");
        }

        if (value.ToString()!.ToLower().Contains("zz"))
        {

            return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName));
        }

        return ValidationResult.Success;
    }
}

다음 코드에서는 사용자 지정 [ValidateName] 특성이 적용됩니다.

public class Contact
{
    public Guid Id { get; set; }

    [ValidateName(ErrorMessage = "Name must not contain `zz`")] 
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? PhoneNumber { get; set; }
}

모델에 포함된 zz경우 새 ValidationResult 모델이 반환됩니다.

최상위 노드 유효성 검사

최상위 수준 노드에는 다음이 포함됩니다.

  • 작업 매개 변수
  • 컨트롤러 속성
  • 페이지 처리기 매개 변수
  • 페이지 모델 속성

모델 바인딩 최상위 노드는 모델 속성의 유효성을 검사하는 것 외에도 유효성이 검사됩니다. 샘플 앱의 다음 예제에서 VerifyPhone 메서드는 RegularExpressionAttribute를 사용하여 phone 작업 매개 변수의 유효성을 검사합니다.

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

최상위 노드는 유효성 검사 특성과 함께 BindRequiredAttribute를 사용할 수 있습니다. 샘플 앱의 다음 예제에서 CheckAge 메서드는 양식을 제출할 때 쿼리 문자열에서 age 매개 변수를 바인딩해야 함을 지정합니다.

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

검사 기간 페이지(CheckAge.cshtml)에는 두 가지 양식이 있습니다. 첫 번째 양식은 99Age 값을 쿼리 문자열 매개 변수(https://localhost:5001/Users/CheckAge?Age=99)로 제출합니다.

쿼리 문자열에서 올바른 서식이 지정된 age 매개 변수를 제출하면 양식이 유효성을 검사합니다.

기간 확인 페이지의 두 번째 양식은 요청 본문의 Age 값을 제출하고 유효성 검사가 실패합니다. age 매개 변수는 쿼리 문자열에서 가져와야 하므로 바인딩이 실패합니다.

최대 오류 수

유효성 검사는 최대 오류 수(기본값 200)에 도달하면 중지됩니다. Program.cs에 다음 코드로 이 번호를 구성할 수 있습니다.

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

최대 재귀

ValidationVisitor는 유효성 검사 중인 모델의 개체 그래프를 트래버스합니다. 깊거나 무한히 재귀하는 모델의 경우 유효성 검사를 실행하면 스택 오버플로가 발생할 수 있습니다. MvcOptions.MaxValidationDepth 는 방문자 재귀가 구성된 깊이를 초과하는 경우 유효성 검사를 일찍 중지하는 방법을 제공합니다. MvcOptions.MaxValidationDepth의 기본값은 32입니다.

자동 단락

모델 그래프에 대한 유효성 검사가 필요하지 않은 경우 유효성 검사는 자동으로 단락됩니다(건너뜀). 런타임에 유효성 검사를 건너뛰는 개체로는 기본 형식(byte[], string[], Dictionary<string, string>) 수집 및 유효성 검사기를 포함하지 않은 복합 개체 그래프가 있습니다.

클라이언트 쪽 유효성 검사

클라이언트 쪽 유효성 검사는 형식이 유효할 때까지 전송을 방지합니다. 제출 단추는 형식을 전송하거나 오류 메시지를 표시하는 JavaScript를 실행합니다.

클라이언트 쪽 유효성 검사는 양식에 대한 입력 오류가 있는 경우 불필요한 서버 왕복을 방지합니다. 다음 스크립트는 클라이언트 쪽 유효성 검사를 참조하고 _ValidationScriptsPartial.cshtml 지원합니다_Layout.cshtml.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

jQuery 비간섭 유효성 검사 스크립트는 널리 사용되는 jQuery 유효성 검사 플러그 인에 기반한 사용자 지정 Microsoft 프런트 엔드 라이브러리입니다. jQuery 비간섭 유효성 검사를 사용하지 않을 경우 두 위치(모델 속성에 대한 서버 쪽 유효성 검사 특성에서 한 번 및 클라이언트 쪽 스크립트에서 다시 한 번)에서 동일한 유효성 검사 논리를 코딩해야 할 수 있습니다. 그 대신 태그 도우미HTML 도우미 는 유효성 검사가 필요한 양식 요소에 대해 모델 특성의 유효성 검사 특성 및 형식 메타데이터를 사용하여 HTML 5 data- 특성을 렌더링합니다. jQuery 비간섭 유효성 검사는 data- 특성을 구문 분석하고 jQuery 유효성 검사로 논리를 전달하여 효과적으로 서버 쪽 유효성 검사 논리를 클라이언트에 “복사”합니다. 다음과 같이 태그 도우미를 사용하여 클라이언트에서 유효성 검사 오류를 표시할 수 있습니다.

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

위의 태그 도우미는 다음 HTML을 렌더링합니다.

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

HTML의 data- 특성 출력은 Movie.ReleaseDate 속성에 대한 유효성 검사 특성에 해당합니다. data-val-required 특성은 사용자가 릴리스 날짜 필드를 입력하지 않았음을 표시하는 오류 메시지를 포함합니다. jQuery 비간섭 유효성 검사는 jQuery 유효성 검사 required() 메서드에 이 값을 전달합니다. 그러면 <span> 요소와 함께 해당 메시지를 표시합니다.

데이터 형식 유효성 검사는 [DataType] 특성에 의해 재정의되지 않은 한, 속성의 .NET 형식을 기반으로 합니다. 브라우저는 고유한 기본 오류 메시지를 가지고 있지만 jQuery유효성 검사의 비간섭 유효성 검사 패키지는 해당 메시지를 재정의할 수 있습니다. [DataType] 특성 및 [EmailAddress]와 같은 서브클래스를 사용하여 오류 메시지를 지정할 수 있습니다.

비간섭 유효성 검사

비간섭 유효성 검사에 대한 자세한 내용은 이 GitHub 문제를 참조하세요.

동적 형식에 유효성 검사 추가

jQuery 비간섭 유효성 검사는 페이지가 먼저 로드되는 경우 유효성 검사 논리 및 매개 변수를 jQuery 유효성 검사에 전달합니다. 따라서 동적으로 생성된 양식에 대해서는 유효성 검사가 자동으로 작동하지 않습니다. 유효성 검사를 사용하도록 설정하려면 jQuery 비간섭 유효성 검사에서 동적 폼을 만든 후에 즉시 구문 분석하도록 지시합니다. 예를 들어 다음 코드에서는 AJAX를 통해 추가된 양식에 대해 클라이언트 쪽 유효성 검사를 설정합니다.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() 메서드는 하나의 인수에 대해 jQuery 선택기를 허용합니다. 이 메서드는 jQuery 비간섭 유효성 검사에서 해당 선택기 내에 있는 형식의 data- 특성을 구문 분석하도록 지시합니다. 그러면 이러한 특성 값이 jQuery 유효성 검사 플러그 인에 전달됩니다.

동적 컨트롤에 유효성 검사 추가

$.validator.unobtrusive.parse() 메서드는 <input><select/>와 같은 동적으로 생성된 개별 컨트롤이 아닌 전체 양식에 대해 작용합니다. 양식을 다시 구문 분석하려면 다음 예제와 같이 이전에 양식을 구문 분석했을 때 추가된 유효성 검사 데이터를 제거합니다.

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

사용자 지정 클라이언트 쪽 유효성 검사

사용자 지정 클라이언트 쪽 유효성 검사는 사용자 지정 jQuery 유효성 검사 어댑터에서 작동하는 data- HTML 특성을 생성하여 수행됩니다. 다음 샘플 어댑터 코드는 이 문서에서 이전에 소개한 [ClassicMovie][ClassicMovieWithClientValidator] 특성용으로 작성되었습니다.

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

어댑터 작성 방법에 대한 자세한 내용은 jQuery 유효성 검사 설명서를 참조하세요.

지정된 필드에 대한 어댑터 사용은 data- 특성에 의해 트리거됩니다.

  • 필드를 유효성 검사가 적용되는 것으로 표시합니다(data-val="true").
  • 유효성 검사 규칙 이름 및 오류 메시지 텍스트(예: data-val-rulename="Error message.")를 식별합니다.
  • 유효섬 검사기에 필요한 추가 매개 변수(예: data-val-rulename-param1="value")를 제공합니다.

다음 예제에서는 샘플 앱의ClassicMovie 특성에 대한 data- 특성을 보여줍니다.

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

앞에서 설명했듯이 태그 도우미HTML 도우미는 유효성 검사 특성의 정보를 사용하여 data- 특성을 렌더링합니다. 사용자 지정 data- HTML 특성을 생성하는 코드를 작성하는 두 가지 방법이 있습니다.

  • AttributeAdapterBase<TAttribute>에서 파생하는 클래스 및 IValidationAttributeAdapterProvider를 구현하는 클래스를 만들고 사용자의 특성 및 해당 어댑터를 DI에 등록합니다. 이 방법은 서버 관련 및 클라이언트 관련 유효성 검사 코드가 별도의 클래스에 있는 단일 책임 보안 주체를 따릅니다. 또한 이 어댑터는 DI에 등록되었으므로 필요한 경우 DI의 다른 서비스를 사용할 수 있다는 이점이 있습니다.
  • 사용자의 ValidationAttribute 클래스에서 IClientModelValidator를 구현합니다. 이 방법은 특성이 서버 쪽 유효성 검사를 수행하지 않고 DI의 서비스가 필요하지 않은 경우 적절할 수 있습니다.

클라이언트 쪽 유효성 검사에 대한 특성 어댑터

이 HTML data- 특성 렌더링 방법은 샘플 앱ClassicMovie 특성에 사용됩니다. 이 방법을 사용하여 클라이언트 유효성 검사를 추가하려면:

  1. 사용자 지정 유효성 검사 특성에 대한 특성 어댑터 클래스를 생성합니다. AttributeAdapterBase<TAttribute>에서 클래스를 파생합니다. 이 예제와 같이 렌더링된 출력에 data- 특성을 추가하는 AddValidation 메서드를 생성합니다.

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. IValidationAttributeAdapterProvider를 구현하는 어댑터 공급자 클래스를 생성합니다. GetAttributeAdapter 메서드에서 다음 예제와 같이 사용자 지정 특성을 어댑터의 생성자에 전달합니다.

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. DI용 어댑터 공급자를 Program.cs에 등록합니다.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

클라이언트 쪽 유효성 검사를 위한 IClientModelValidator

이 HTML data- 특성 렌더링 방법은 샘플 앱ClassicMovieWithClientValidator 특성에 사용됩니다. 이 방법을 사용하여 클라이언트 유효성 검사를 추가하려면:

  • 사용자 지정 유효성 검사 특성에서 IClientModelValidator 인터페이스를 구현하고 AddValidation 메서드를 생성합니다. AddValidation 메서드에서 다음 예제와 같이 유효성 검사를 위한 data- 특성을 추가합니다.

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

클라이언트 쪽 유효성 검사를 사용하지 않도록 설정

다음 코드는 Razor Pages에서 클라이언트 유효성 검사를 사용하지 않도록 설정합니다.

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

클라이언트 쪽 유효성 검사를 사용하지 않도록 설정하는 다른 옵션:

  • 모든 파일에 대한 참조를 _ValidationScriptsPartial 주석으로 처리합니다 .cshtml .
  • Pages\Shared_ValidationScriptsPartial.cshtml 파일의 콘텐츠를 제거합니다.

위의 방법으로는 ASP.NET Core IdentityRazor 클래스 라이브러리의 클라이언트 쪽 유효성 검사를 방지할 수 없습니다. 자세한 내용은 ASP.NET Core 프로젝트에서 Identity 스캐폴드를 참조하세요.

문제점 세부 정보

문제 세부 정보는 HTTP API 오류를 설명하는 유일한 응답 형식은 아니지만 일반적으로 HTTP API에 대한 오류를 보고하는 데 사용됩니다.

문제 세부 정보 서비스는 ASP.NET Core 문제 세부 정보 만들기를 지원하는 IProblemDetailsService 인터페이스를 구현합니다. IServiceCollectionAddProblemDetails 확장 메서드는 기본 IProblemDetailsService 구현을 등록합니다.

ASP.NET Core 앱에서 다음 미들웨어는 Accept이 등록된 IProblemDetailsWriter(기본값: application/json)가 지원되는 콘텐츠 형식 중 하나가 포함되지 않은 HTTP 헤더를 요청하는 경우를 제외하고 AddProblemDetails가 호출될 때 문제 세부 정보 HTTP 응답을 생성합니다.

추가 리소스

이 문서에서는 ASP.NET Core MVC 또는 Razor Pages 앱에서 사용자 입력의 유효성을 검사하는 방법에 대해 설명합니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법). 다운로드 예제는 영역을 테스트하기 위한 기초적인 앱을 제공합니다.

모델 상태

모델 상태는 모델 바인딩 및 모델 유효성 검사 두 하위 시스템에서 발생하는 오류를 나타냅니다. 모델 바인딩에서 발생하는 오류는 일반적으로 데이터 변환 오류입니다. 예를 들어 정수 필드에 “x”가 입력됩니다. 모델 유효성 검사는 모델 바인딩 후에 발생하며 데이터가 비즈니스 규칙에 맞지 않으면 오류를 보고합니다. 예를 들어 1에서 5 사이의 등급을 필요한 필드에 0이 입력됩니다.

모델 바인딩과 모델 유효성 검사 모두 컨트롤러 작업 또는 Razor Pages 처리기 메서드를 실행하기 전에 실행됩니다. 웹앱의 경우 ModelState.IsValid를 검사하고 적절히 반응하는 기능은 앱에서 실행합니다. 웹앱은 일반적으로 다음 Razor Pages 예제와 같이 오류 메시지로 페이지를 다시 표시합니다.

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

컨트롤러 및 뷰가 있는 ASP.NET Core MVC의 경우 다음 예제에서는 컨트롤러 작업 내부에서 ModelState.IsValid를 확인하는 방법을 보여 줍니다.

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

웹 API 컨트롤러는 [ApiController] 특성을 포함하는 경우 ModelState.IsValid를 확인할 필요가 없습니다. 이 경우 모델 상태가 잘못되면 오류 세부 정보를 포함하는 자동 HTTP 400 응답이 반환됩니다. 자세한 정보는 자동 HTTP 400 응답을 참조하세요.

유효성 검사 다시 실행

유효성 검사는 자동으로 실행 되지만 수동으로 반복하는 것이 좋습니다. 예를 들어 속성에 대한 값을 계산하고 해당 속성을 계산된 값으로 설정한 후 유효성 검사를 다시 실행하는 것이 좋습니다. 유효성 검사를 다시 실행하려면 ModelStateDictionary.ClearValidationState를 호출하여 TryValidateModel에 따라 유효성이 검사되는 모델에 대한 특정 유효성 검사를 제거합니다.

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

유효성 검사 특성

유효성 검사 특성을 사용하여 모델 속성에 대한 유효성 검사 규칙을 지정할 수 있습니다. 샘플 앱의 다음 예제는 유효성 검사 특성으로 주석을 단 모델 클래스를 나타냅니다. [ClassicMovie] 특성은 사용자 지정 유효성 검사 특성이며 다른 특성은 기본 제공 특성입니다. 사용자 지정 특성을 구현하는 다른 방법을 보여 주는 [ClassicMovieWithClientValidator]는 표시되지 않습니다.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

기본 제공 특성

다음은 몇 가지 기본 제공 유효성 검사 특성입니다.

  • [ValidateNever]: 속성 또는 매개 변수가 유효성 검사에서 제외되어야 함을 나타냅니다.
  • [CreditCard]: 속성에 신용 카드 형식이 있는지 유효성을 검사합니다. jQuery 유효성 검사 추가 방법이 필요합니다.
  • [Compare]: 모델의 두 속성이 일치하는지 유효성을 검사합니다.
  • [EmailAddress]: 속성에 전자 메일 형식이 있는지 유효성을 검사합니다.
  • [Phone]: 속성에 전화 번호 형식이 있는지 유효성을 검사합니다.
  • [Range]: 속성 값이 지정된 범위 내에 포함되는지 유효성을 검사합니다.
  • [RegularExpression]: 속성 값이 지정된 정규 식과 일치하는지 유효성을 검사합니다.
  • [Required]: 필드가 null이 아닌지 확인합니다. 이 특성의 동작에 대한 자세한 내용은 [Required] 특성을 참조하세요.
  • [StringLength]: 문자열 속성 값이 지정된 길이 한계를 초과하지 않는지 유효성을 검사합니다.
  • [Url]: 속성에 URL 형식이 있는지 유효성을 검사합니다.
  • [Remote]: 서버에 대한 작업 명령을 호출하여 클라이언트에 대한 입력의 유효성을 검사합니다. 이 특성의 동작에 대한 자세한 내용은 [Remote] 특성을 참조하세요.

네임스페이스에서 유효성 검사 특성의 전체 목록을 찾을 System.ComponentModel.DataAnnotations 수 있습니다.

오류 메시지

유효성 검사 특성을 사용하여 잘못된 입력에 대해 표시할 오류 메시지를 지정할 수 있습니다. 예시:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

내부적으로 이 특성은 필드 이름에 대한 자리 표시자 및 경우에 따라 추가 자리 표시자를 사용하여 String.Format을 호출합니다. 예시:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Name 속성에 적용할 경우 위의 코드에 의해 생성되는 오류 메시지는 "이름의 길이는 6문자에서 8문자 사이여야 합니다"일 것입니다.

특정 특성의 오류 메시지에 대해 String.Format 에 전달되는 매개 변수를 알아보려면 DataAnnotations 소스 코드를 참조하세요.

Null을 허용하지 않는 참조 형식 및 [Required] 특성

유효성 검사 시스템은 Null을 허용하지 않는 매개 변수 또는 바인딩되는 속성을 [Required(AllowEmptyStrings = true)] 특성을 포함한 것처럼 처리합니다. Nullable 컨텍스트를 사용하도록 설정하면 MVC는 [Required(AllowEmptyStrings = true)] 특성을 사용하는 것처럼 암시적으로 null을 허용하지 않는 속성 비 제네릭 유형 또는 매개 변수의 유효성 검사를 시작합니다. 다음 코드를 생각해 봅시다.

public class Person
{
    public string Name { get; set; }
}

앱이 <Nullable>enable</Nullable>로 빌드된 경우 JSON 또는 양식 게시의 Name에 대한 값이 누락되면 유효성 검사 오류가 발생합니다. Null 허용 참조 형식을 사용하여 Name 속성에 null 또는 누락된 값을 지정할 수 있습니다.

public class Person
{
    public string? Name { get; set; }
}

Program.cs에서 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes를 구성하여 이 동작을 사용하지 않도록 설정할 수 있습니다.

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

제네릭 형식 및 [Required] 특성에서 nullable이 아닌 속성

제네릭 형식의 nullable이 아닌 속성은 형식이 필요한 경우 [Required] 특성을 포함해야 합니다. 다음 코드에서는 TestRequired가 필요하지 않습니다.

public class WeatherForecast<T>
{
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

다음 코드에서 TestRequired는 명시적으로 필수로 표시됩니다.

using System.ComponentModel.DataAnnotations;

public class WeatherForecast<T>
{
    [Required]
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

서버에 대한 [Required] 유효성 검사

서버에서 속성이 Null인 경우 필수 값이 없는 것으로 간주합니다. Null을 허용하지 않는 필드는 언제나 유효하며 [Required] 특성의 오류 메시지는 표시되지 않습니다.

그러나 Null을 허용하지 않는 속성에 대한 모델 바인딩이 실패하여 The value '' is invalid와 같은 오류 메시지가 표시될 수 있습니다. Null을 허용하지 않는 형식의 서버 쪽 유효성 검사에 대한 사용자 지정 오류 메시지를 지정하려는 경우 다음과 같은 방법이 있습니다.

  • 필드를 Null 허용으로 만듭니다(예: decimal 대신에 decimal?). Null을 허용하는 <T> 값 형식은 표준 Nul을 허용 형식처럼 처리됩니다.

  • 다음 예제에서와 같이 모델 바인딩에 의해 사용할 기본 오류 메시지를 지정합니다.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    기본 메시지를 설정할 수 있는 모델 바인딩 오류에 대한 자세한 내용은 DefaultModelBindingMessageProvider를 참조하십시오.

클라이언트에 대한 [Required] 유효성 검사

클라이언트에서 Null을 허용하지 않는 형식 및 문자열은 서버의 경우와 다르게 처리됩니다. 클라이언트에서:

  • 값은 해당 값에 대한 입력이 있는 경우에만 존재하는 것으로 간주됩니다. 그러므로 클라이언트 쪽 유효성 검사에서는 Null을 허용하지 않는 형식을 Null 허용 형식과 동일하게 처리합니다.
  • 문자열 필드의 공백은 jQuery 유효성 검사 필수 메서드에 의해 유효한 입력으로 간주됩니다. 서버 쪽 유효성 검사에서는 공백만 입력된 경우 필수 문자열 필드를 잘못된 것으로 간주합니다.

위에서 설명했듯이 Null을 허용하지 않는 형식은 [Required(AllowEmptyStrings = true)] 특성을 포함한 것처럼 처리됩니다. 즉, [Required(AllowEmptyStrings = true)] 특성을 적용하지 않더라도 클라이언트 쪽 유효성 검사가 이루어집니다. 그러나 해당 특성을 사용하지 않으면 기본 오류 메시지가 표시됩니다. 사용자 지정 오류 메시지를 지정하려면 해당 특성을 사용하십시오.

[Remote] 특성

[Remote] 특성은 필드 입력이 유효한지 여부를 결정하기 위해 서버에서 메서드를 호출해야 하는 클라이언트 쪽 유효성 검사를 구현합니다. 예를 들어 앱에서 사용자 이름을 이미 사용 중인지 여부를 확인해야 할 수 있습니다.

원격 유효성 검사를 구현하려면:

  1. JavaScript가 호출할 작업 메서드를 만듭니다. jQuery 유효성 검사 remote 메서드는 JSON 응답을 수신해야 합니다.

    • true는 입력 데이터가 유효함을 의미합니다.
    • false, undefined 또는 null은 입력이 잘못되었음을 의미합니다. 기본 오류 메시지를 표시합니다.
    • 다른 문자열은 모두 입력이 잘못되었음을 의미합니다. 문자열을 사용자 지정 오류 메시지로 표시합니다.

    다음은 사용자 지정 오류 메시지를 반환하는 작업 메서드의 예입니다.

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 다음 예제와 같이 모델 클래스에서는 유효성 검사 작업 메서드를 가리키는 [Remote] 특성으로 속성에 주석을 답니다.

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

추가 필드

[Remote] 특성의 AdditionalFields 속성을 사용하여 서버의 데이터를 기준으로 필드 조합의 유효성을 검사할 수 있습니다. 예를 들어 User 모델이 FirstNameLastName 속성을 포함한 경우 해당 이름 쌍을 이미 가진 기존 사용자가 없는지 확인하는 것이 좋습니다. 다음 예제에서는 AdditionalFields을 사용하는 방법을 보여 줍니다.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields는 명시적으로 믄자열 "FirstName" 및 "LastName"으로 설정할 수 있지만 nameof 연산자를 사용하면 나중에 리팩터링이 간단해집니다. 이 유효성 검사의 작업 메서드는 firstNamelastName 인수를 모두 허용해야 합니다.

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

사용자가 이름 또는 성을 입력하면 JavaScript에서는 원격 호출을 실행하여 해당 이름 쌍이 사용 중인지 확인합니다.

두 개 이상의 추가 필드에 대한 유효성을 검사하려면 해당 필드를 쉼표로 구분된 목록으로 제공합니다. 예를 들어 MiddleName 속성을 모델에 추가하려면 [Remote] 특성을 다음 예제와 같이 설정합니다.

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

모든 특성 인수와 같은 AdditionalFields은 상수 식이어야 합니다. 따라서 AdditionalFields를 초기화하기 위해 보간된 문자열을 사용하거나 Join을 호출하지 마십시오.

기본 제공 특성에 대한 대안

기본 제공 특성이 제공하지 않는 유효성 검사가 필요한 경우 다음과 같이 할 수 있습니다.

사용자 지정 특성

기본 제공 유효성 검사 특성이 처리하지 않는 시나리오의 경우 사용자 지정 유효성 검사 특성을 만들 수 있습니다. ValidationAttribute에서 상속하는 클래스를 만들고 IsValid 메서드를 재정의합니다.

IsValid 메서드에 대해서는 유효성을 검사할 입력인 이라는 개체를 지정할 수 있습니다. 또한 오버로드에 대해서는 모델 바인딩에 의해 생성된 모델 인스턴스와 같은 추가 정보를 제공하는 ValidationContext 개체를 지정할 수 있습니다.

다음 예제에서는 클래식 장르의 영화에 대한 개봉 날짜가 지정된 연도보다 이후가 아닌지 유효성을 검사합니다. [ClassicMovie] 특성:

  • 서버에서만 실행됩니다.
  • 클래식 동영상의 경우 릴리스 날짜의 유효성을 검사합니다.
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

위의 예제에서 movie 변수는 양식 제출에서 제공된 데이터를 포함하는 Movie 개체를 나타냅니다. 유효성 검사에 실패하면 오류 메시지가 발생하여 ValidationResult가 반환됩니다.

IValidatableObject

앞의 예제는 Movie 형식에 대해서만 작동합니다. 또 다른 클래스 수준 유효성 검사 방법은 다음 예제와 같이 모델 클래스 내에 IValidatableObject를 구현하는 것입니다.

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

최상위 노드 유효성 검사

최상위 수준 노드에는 다음이 포함됩니다.

  • 작업 매개 변수
  • 컨트롤러 속성
  • 페이지 처리기 매개 변수
  • 페이지 모델 속성

모델 바인딩 최상위 노드는 모델 속성의 유효성을 검사하는 것 외에도 유효성이 검사됩니다. 샘플 앱의 다음 예제에서 VerifyPhone 메서드는 RegularExpressionAttribute를 사용하여 phone 작업 매개 변수의 유효성을 검사합니다.

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

최상위 노드는 유효성 검사 특성과 함께 BindRequiredAttribute를 사용할 수 있습니다. 샘플 앱의 다음 예제에서 CheckAge 메서드는 양식을 제출할 때 쿼리 문자열에서 age 매개 변수를 바인딩해야 함을 지정합니다.

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

검사 기간 페이지(CheckAge.cshtml)에는 두 가지 양식이 있습니다. 첫 번째 양식은 99Age 값을 쿼리 문자열 매개 변수(https://localhost:5001/Users/CheckAge?Age=99)로 제출합니다.

쿼리 문자열에서 올바른 서식이 지정된 age 매개 변수를 제출하면 양식이 유효성을 검사합니다.

기간 확인 페이지의 두 번째 양식은 요청 본문의 Age 값을 제출하고 유효성 검사가 실패합니다. age 매개 변수는 쿼리 문자열에서 가져와야 하므로 바인딩이 실패합니다.

최대 오류 수

유효성 검사는 최대 오류 수(기본값 200)에 도달하면 중지됩니다. Program.cs에 다음 코드로 이 번호를 구성할 수 있습니다.

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

최대 재귀

ValidationVisitor는 유효성 검사 중인 모델의 개체 그래프를 트래버스합니다. 깊거나 무한히 재귀하는 모델의 경우 유효성 검사를 실행하면 스택 오버플로가 발생할 수 있습니다. MvcOptions.MaxValidationDepth 는 방문자 재귀가 구성된 깊이를 초과하는 경우 유효성 검사를 일찍 중지하는 방법을 제공합니다. MvcOptions.MaxValidationDepth의 기본값은 32입니다.

자동 단락

모델 그래프에 대한 유효성 검사가 필요하지 않은 경우 유효성 검사는 자동으로 단락됩니다(건너뜀). 런타임에 유효성 검사를 건너뛰는 개체로는 기본 형식(byte[], string[], Dictionary<string, string>) 수집 및 유효성 검사기를 포함하지 않은 복합 개체 그래프가 있습니다.

클라이언트 쪽 유효성 검사

클라이언트 쪽 유효성 검사는 형식이 유효할 때까지 전송을 방지합니다. 제출 단추는 형식을 전송하거나 오류 메시지를 표시하는 JavaScript를 실행합니다.

클라이언트 쪽 유효성 검사는 양식에 대한 입력 오류가 있는 경우 불필요한 서버 왕복을 방지합니다. 다음 스크립트는 클라이언트 쪽 유효성 검사를 참조하고 _ValidationScriptsPartial.cshtml 지원합니다_Layout.cshtml.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

jQuery 비간섭 유효성 검사 스크립트는 널리 사용되는 jQuery 유효성 검사 플러그 인에 기반한 사용자 지정 Microsoft 프런트 엔드 라이브러리입니다. jQuery 비간섭 유효성 검사를 사용하지 않을 경우 두 위치(모델 속성에 대한 서버 쪽 유효성 검사 특성에서 한 번 및 클라이언트 쪽 스크립트에서 다시 한 번)에서 동일한 유효성 검사 논리를 코딩해야 할 수 있습니다. 그 대신 태그 도우미HTML 도우미 는 유효성 검사가 필요한 양식 요소에 대해 모델 특성의 유효성 검사 특성 및 형식 메타데이터를 사용하여 HTML 5 data- 특성을 렌더링합니다. jQuery 비간섭 유효성 검사는 data- 특성을 구문 분석하고 jQuery 유효성 검사로 논리를 전달하여 효과적으로 서버 쪽 유효성 검사 논리를 클라이언트에 “복사”합니다. 다음과 같이 태그 도우미를 사용하여 클라이언트에서 유효성 검사 오류를 표시할 수 있습니다.

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

위의 태그 도우미는 다음 HTML을 렌더링합니다.

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

HTML의 data- 특성 출력은 Movie.ReleaseDate 속성에 대한 유효성 검사 특성에 해당합니다. data-val-required 특성은 사용자가 릴리스 날짜 필드를 입력하지 않았음을 표시하는 오류 메시지를 포함합니다. jQuery 비간섭 유효성 검사는 jQuery 유효성 검사 required() 메서드에 이 값을 전달합니다. 그러면 <span> 요소와 함께 해당 메시지를 표시합니다.

데이터 형식 유효성 검사는 [DataType] 특성에 의해 재정의되지 않은 한, 속성의 .NET 형식을 기반으로 합니다. 브라우저는 고유한 기본 오류 메시지를 가지고 있지만 jQuery유효성 검사의 비간섭 유효성 검사 패키지는 해당 메시지를 재정의할 수 있습니다. [DataType] 특성 및 [EmailAddress]와 같은 서브클래스를 사용하여 오류 메시지를 지정할 수 있습니다.

비간섭 유효성 검사

비간섭 유효성 검사에 대한 자세한 내용은 이 GitHub 문제를 참조하세요.

동적 형식에 유효성 검사 추가

jQuery 비간섭 유효성 검사는 페이지가 먼저 로드되는 경우 유효성 검사 논리 및 매개 변수를 jQuery 유효성 검사에 전달합니다. 따라서 동적으로 생성된 양식에 대해서는 유효성 검사가 자동으로 작동하지 않습니다. 유효성 검사를 사용하도록 설정하려면 jQuery 비간섭 유효성 검사에서 동적 폼을 만든 후에 즉시 구문 분석하도록 지시합니다. 예를 들어 다음 코드에서는 AJAX를 통해 추가된 양식에 대해 클라이언트 쪽 유효성 검사를 설정합니다.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() 메서드는 하나의 인수에 대해 jQuery 선택기를 허용합니다. 이 메서드는 jQuery 비간섭 유효성 검사에서 해당 선택기 내에 있는 형식의 data- 특성을 구문 분석하도록 지시합니다. 그러면 이러한 특성 값이 jQuery 유효성 검사 플러그 인에 전달됩니다.

동적 컨트롤에 유효성 검사 추가

$.validator.unobtrusive.parse() 메서드는 <input><select/>와 같은 동적으로 생성된 개별 컨트롤이 아닌 전체 양식에 대해 작용합니다. 양식을 다시 구문 분석하려면 다음 예제와 같이 이전에 양식을 구문 분석했을 때 추가된 유효성 검사 데이터를 제거합니다.

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

사용자 지정 클라이언트 쪽 유효성 검사

사용자 지정 클라이언트 쪽 유효성 검사는 사용자 지정 jQuery 유효성 검사 어댑터에서 작동하는 data- HTML 특성을 생성하여 수행됩니다. 다음 샘플 어댑터 코드는 이 문서에서 이전에 소개한 [ClassicMovie][ClassicMovieWithClientValidator] 특성용으로 작성되었습니다.

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

어댑터 작성 방법에 대한 자세한 내용은 jQuery 유효성 검사 설명서를 참조하세요.

지정된 필드에 대한 어댑터 사용은 data- 특성에 의해 트리거됩니다.

  • 필드를 유효성 검사가 적용되는 것으로 표시합니다(data-val="true").
  • 유효성 검사 규칙 이름 및 오류 메시지 텍스트(예: data-val-rulename="Error message.")를 식별합니다.
  • 유효섬 검사기에 필요한 추가 매개 변수(예: data-val-rulename-param1="value")를 제공합니다.

다음 예제에서는 샘플 앱의ClassicMovie 특성에 대한 data- 특성을 보여줍니다.

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

앞에서 설명했듯이 태그 도우미HTML 도우미는 유효성 검사 특성의 정보를 사용하여 data- 특성을 렌더링합니다. 사용자 지정 data- HTML 특성을 생성하는 코드를 작성하는 두 가지 방법이 있습니다.

  • AttributeAdapterBase<TAttribute>에서 파생하는 클래스 및 IValidationAttributeAdapterProvider를 구현하는 클래스를 만들고 사용자의 특성 및 해당 어댑터를 DI에 등록합니다. 이 방법은 서버 관련 및 클라이언트 관련 유효성 검사 코드가 별도의 클래스에 있는 단일 책임 보안 주체를 따릅니다. 또한 이 어댑터는 DI에 등록되었으므로 필요한 경우 DI의 다른 서비스를 사용할 수 있다는 이점이 있습니다.
  • 사용자의 ValidationAttribute 클래스에서 IClientModelValidator를 구현합니다. 이 방법은 특성이 서버 쪽 유효성 검사를 수행하지 않고 DI의 서비스가 필요하지 않은 경우 적절할 수 있습니다.

클라이언트 쪽 유효성 검사에 대한 특성 어댑터

이 HTML data- 특성 렌더링 방법은 샘플 앱ClassicMovie 특성에 사용됩니다. 이 방법을 사용하여 클라이언트 유효성 검사를 추가하려면:

  1. 사용자 지정 유효성 검사 특성에 대한 특성 어댑터 클래스를 생성합니다. AttributeAdapterBase<TAttribute>에서 클래스를 파생합니다. 이 예제와 같이 렌더링된 출력에 data- 특성을 추가하는 AddValidation 메서드를 생성합니다.

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. IValidationAttributeAdapterProvider를 구현하는 어댑터 공급자 클래스를 생성합니다. GetAttributeAdapter 메서드에서 다음 예제와 같이 사용자 지정 특성을 어댑터의 생성자에 전달합니다.

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. DI용 어댑터 공급자를 Program.cs에 등록합니다.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

클라이언트 쪽 유효성 검사를 위한 IClientModelValidator

이 HTML data- 특성 렌더링 방법은 샘플 앱ClassicMovieWithClientValidator 특성에 사용됩니다. 이 방법을 사용하여 클라이언트 유효성 검사를 추가하려면:

  • 사용자 지정 유효성 검사 특성에서 IClientModelValidator 인터페이스를 구현하고 AddValidation 메서드를 생성합니다. AddValidation 메서드에서 다음 예제와 같이 유효성 검사를 위한 data- 특성을 추가합니다.

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

클라이언트 쪽 유효성 검사를 사용하지 않도록 설정

다음 코드는 Razor Pages에서 클라이언트 유효성 검사를 사용하지 않도록 설정합니다.

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

클라이언트 쪽 유효성 검사를 사용하지 않도록 설정하는 다른 옵션:

  • 모든 파일에 대한 참조를 _ValidationScriptsPartial 주석으로 처리합니다 .cshtml .
  • Pages\Shared_ValidationScriptsPartial.cshtml 파일의 콘텐츠를 제거합니다.

위의 방법으로는 ASP.NET Core IdentityRazor 클래스 라이브러리의 클라이언트 쪽 유효성 검사를 방지할 수 없습니다. 자세한 내용은 ASP.NET Core 프로젝트에서 Identity 스캐폴드를 참조하세요.

추가 리소스

이 문서에서는 ASP.NET Core MVC 또는 Razor Pages 앱에서 사용자 입력의 유효성을 검사하는 방법에 대해 설명합니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법). 다운로드 예제는 영역을 테스트하기 위한 기초적인 앱을 제공합니다.

모델 상태

모델 상태는 모델 바인딩 및 모델 유효성 검사 두 하위 시스템에서 발생하는 오류를 나타냅니다. 모델 바인딩에서 발생하는 오류는 일반적으로 데이터 변환 오류입니다. 예를 들어 정수 필드에 “x”가 입력됩니다. 모델 유효성 검사는 모델 바인딩 후에 발생하며 데이터가 비즈니스 규칙에 맞지 않으면 오류를 보고합니다. 예를 들어 1에서 5 사이의 등급을 필요한 필드에 0이 입력됩니다.

모델 바인딩과 모델 유효성 검사 모두 컨트롤러 작업 또는 Razor Pages 처리기 메서드를 실행하기 전에 실행됩니다. 웹앱의 경우 ModelState.IsValid를 검사하고 적절히 반응하는 기능은 앱에서 실행합니다. 일반적으로 웹앱은 페이지를 오류 메시지와 함께 다시 표시합니다.

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

웹 API 컨트롤러는 [ApiController] 특성을 포함하는 경우 ModelState.IsValid를 확인할 필요가 없습니다. 이 경우 모델 상태가 잘못되면 오류 세부 정보를 포함하는 자동 HTTP 400 응답이 반환됩니다. 자세한 정보는 자동 HTTP 400 응답을 참조하세요.

유효성 검사 다시 실행

유효성 검사는 자동으로 실행 되지만 수동으로 반복하는 것이 좋습니다. 예를 들어 속성에 대한 값을 계산하고 해당 속성을 계산된 값으로 설정한 후 유효성 검사를 다시 실행하는 것이 좋습니다. 유효성 검사를 다시 실행하려면 ModelStateDictionary.ClearValidationState를 호출하여 TryValidateModel에 따라 유효성이 검사되는 모델에 대한 특정 유효성 검사를 제거합니다.

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

유효성 검사 특성

유효성 검사 특성을 사용하여 모델 속성에 대한 유효성 검사 규칙을 지정할 수 있습니다. 샘플 앱의 다음 예제는 유효성 검사 특성으로 주석을 단 모델 클래스를 나타냅니다. [ClassicMovie] 특성은 사용자 지정 유효성 검사 특성이며 다른 특성은 기본 제공 특성입니다. 사용자 지정 특성을 구현하는 다른 방법을 보여 주는 [ClassicMovieWithClientValidator]는 표시되지 않습니다.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

기본 제공 특성

다음은 몇 가지 기본 제공 유효성 검사 특성입니다.

  • [ValidateNever]: 속성 또는 매개 변수가 유효성 검사에서 제외되어야 함을 나타냅니다.
  • [CreditCard]: 속성에 신용 카드 형식이 있는지 유효성을 검사합니다. jQuery 유효성 검사 추가 방법이 필요합니다.
  • [Compare]: 모델의 두 속성이 일치하는지 유효성을 검사합니다.
  • [EmailAddress]: 속성에 전자 메일 형식이 있는지 유효성을 검사합니다.
  • [Phone]: 속성에 전화 번호 형식이 있는지 유효성을 검사합니다.
  • [Range]: 속성 값이 지정된 범위 내에 포함되는지 유효성을 검사합니다.
  • [RegularExpression]: 속성 값이 지정된 정규 식과 일치하는지 유효성을 검사합니다.
  • [Required]: 필드가 null이 아닌지 확인합니다. 이 특성의 동작에 대한 자세한 내용은 [Required] 특성을 참조하세요.
  • [StringLength]: 문자열 속성 값이 지정된 길이 한계를 초과하지 않는지 유효성을 검사합니다.
  • [Url]: 속성에 URL 형식이 있는지 유효성을 검사합니다.
  • [Remote]: 서버에 대한 작업 명령을 호출하여 클라이언트에 대한 입력의 유효성을 검사합니다. 이 특성의 동작에 대한 자세한 내용은 [Remote] 특성을 참조하세요.

네임스페이스에서 유효성 검사 특성의 전체 목록을 찾을 System.ComponentModel.DataAnnotations 수 있습니다.

오류 메시지

유효성 검사 특성을 사용하여 잘못된 입력에 대해 표시할 오류 메시지를 지정할 수 있습니다. 예시:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

내부적으로 이 특성은 필드 이름에 대한 자리 표시자 및 경우에 따라 추가 자리 표시자를 사용하여 String.Format을 호출합니다. 예시:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Name 속성에 적용할 경우 위의 코드에 의해 생성되는 오류 메시지는 "이름의 길이는 6문자에서 8문자 사이여야 합니다"일 것입니다.

특정 특성의 오류 메시지에 대해 String.Format 에 전달되는 매개 변수를 알아보려면 DataAnnotations 소스 코드를 참조하세요.

Null을 허용하지 않는 참조 형식 및 [Required] 특성

유효성 검사 시스템은 Null을 허용하지 않는 매개 변수 또는 바인딩되는 속성을 [Required(AllowEmptyStrings = true)] 특성을 포함한 것처럼 처리합니다. Nullable 컨텍스트를 사용하도록 설정하면 MVC는 [Required(AllowEmptyStrings = true)] 특성을 사용하는 것처럼 암시적으로 null을 허용하지 않는 속성 또는 매개 변수의 유효성 검사를 시작합니다. 다음 코드를 생각해 봅시다.

public class Person
{
    public string Name { get; set; }
}

앱이 <Nullable>enable</Nullable>로 빌드된 경우 JSON 또는 양식 게시의 Name에 대한 값이 누락되면 유효성 검사 오류가 발생합니다. Null 허용 참조 형식을 사용하여 Name 속성에 null 또는 누락된 값을 지정할 수 있습니다.

public class Person
{
    public string? Name { get; set; }
}

Startup.ConfigureServices에서 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes를 구성하여 이 동작을 사용하지 않도록 설정할 수 있습니다.

services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

서버에 대한 [Required] 유효성 검사

서버에서 속성이 Null인 경우 필수 값이 없는 것으로 간주합니다. Null을 허용하지 않는 필드는 언제나 유효하며 [Required] 특성의 오류 메시지는 표시되지 않습니다.

그러나 Null을 허용하지 않는 속성에 대한 모델 바인딩이 실패하여 The value '' is invalid와 같은 오류 메시지가 표시될 수 있습니다. Null을 허용하지 않는 형식의 서버 쪽 유효성 검사에 대한 사용자 지정 오류 메시지를 지정하려는 경우 다음과 같은 방법이 있습니다.

  • 필드를 Null 허용으로 만듭니다(예: decimal 대신에 decimal?). Null을 허용하는 <T> 값 형식은 표준 Nul을 허용 형식처럼 처리됩니다.

  • 다음 예제에서와 같이 모델 바인딩에 의해 사용할 기본 오류 메시지를 지정합니다.

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

    기본 메시지를 설정할 수 있는 모델 바인딩 오류에 대한 자세한 내용은 DefaultModelBindingMessageProvider를 참조하십시오.

클라이언트에 대한 [Required] 유효성 검사

클라이언트에서 Null을 허용하지 않는 형식 및 문자열은 서버의 경우와 다르게 처리됩니다. 클라이언트에서:

  • 값은 해당 값에 대한 입력이 있는 경우에만 존재하는 것으로 간주됩니다. 그러므로 클라이언트 쪽 유효성 검사에서는 Null을 허용하지 않는 형식을 Null 허용 형식과 동일하게 처리합니다.
  • 문자열 필드의 공백은 jQuery 유효성 검사 필수 메서드에 의해 유효한 입력으로 간주됩니다. 서버 쪽 유효성 검사에서는 공백만 입력된 경우 필수 문자열 필드를 잘못된 것으로 간주합니다.

위에서 설명했듯이 Null을 허용하지 않는 형식은 [Required(AllowEmptyStrings = true)] 특성을 포함한 것처럼 처리됩니다. 즉, [Required(AllowEmptyStrings = true)] 특성을 적용하지 않더라도 클라이언트 쪽 유효성 검사가 이루어집니다. 그러나 해당 특성을 사용하지 않으면 기본 오류 메시지가 표시됩니다. 사용자 지정 오류 메시지를 지정하려면 해당 특성을 사용하십시오.

[Remote] 특성

[Remote] 특성은 필드 입력이 유효한지 여부를 결정하기 위해 서버에서 메서드를 호출해야 하는 클라이언트 쪽 유효성 검사를 구현합니다. 예를 들어 앱에서 사용자 이름을 이미 사용 중인지 여부를 확인해야 할 수 있습니다.

원격 유효성 검사를 구현하려면:

  1. JavaScript가 호출할 작업 메서드를 만듭니다. jQuery 유효성 검사 remote 메서드는 JSON 응답을 수신해야 합니다.

    • true는 입력 데이터가 유효함을 의미합니다.
    • false, undefined 또는 null은 입력이 잘못되었음을 의미합니다. 기본 오류 메시지를 표시합니다.
    • 다른 문자열은 모두 입력이 잘못되었음을 의미합니다. 문자열을 사용자 지정 오류 메시지로 표시합니다.

    다음은 사용자 지정 오류 메시지를 반환하는 작업 메서드의 예입니다.

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 다음 예제와 같이 모델 클래스에서는 유효성 검사 작업 메서드를 가리키는 [Remote] 특성으로 속성에 주석을 답니다.

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

추가 필드

[Remote] 특성의 AdditionalFields 속성을 사용하여 서버의 데이터를 기준으로 필드 조합의 유효성을 검사할 수 있습니다. 예를 들어 User 모델이 FirstNameLastName 속성을 포함한 경우 해당 이름 쌍을 이미 가진 기존 사용자가 없는지 확인하는 것이 좋습니다. 다음 예제에서는 AdditionalFields을 사용하는 방법을 보여 줍니다.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; }

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; }

AdditionalFields는 명시적으로 믄자열 "FirstName" 및 "LastName"으로 설정할 수 있지만 nameof 연산자를 사용하면 나중에 리팩터링이 간단해집니다. 이 유효성 검사의 작업 메서드는 firstNamelastName 인수를 모두 허용해야 합니다.

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

사용자가 이름 또는 성을 입력하면 JavaScript에서는 원격 호출을 실행하여 해당 이름 쌍이 사용 중인지 확인합니다.

두 개 이상의 추가 필드에 대한 유효성을 검사하려면 해당 필드를 쉼표로 구분된 목록으로 제공합니다. 예를 들어 MiddleName 속성을 모델에 추가하려면 [Remote] 특성을 다음 예제와 같이 설정합니다.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

모든 특성 인수와 같은 AdditionalFields은 상수 식이어야 합니다. 따라서 AdditionalFields를 초기화하기 위해 보간된 문자열을 사용하거나 Join을 호출하지 마십시오.

기본 제공 특성에 대한 대안

기본 제공 특성이 제공하지 않는 유효성 검사가 필요한 경우 다음과 같이 할 수 있습니다.

사용자 지정 특성

기본 제공 유효성 검사 특성이 처리하지 않는 시나리오의 경우 사용자 지정 유효성 검사 특성을 만들 수 있습니다. ValidationAttribute에서 상속하는 클래스를 만들고 IsValid 메서드를 재정의합니다.

IsValid 메서드에 대해서는 유효성을 검사할 입력인 이라는 개체를 지정할 수 있습니다. 또한 오버로드에 대해서는 모델 바인딩에 의해 생성된 모델 인스턴스와 같은 추가 정보를 제공하는 ValidationContext 개체를 지정할 수 있습니다.

다음 예제에서는 클래식 장르의 영화에 대한 개봉 날짜가 지정된 연도보다 이후가 아닌지 유효성을 검사합니다. [ClassicMovie] 특성:

  • 서버에서만 실행됩니다.
  • 클래식 동영상의 경우 릴리스 날짜의 유효성을 검사합니다.
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
    {
        Year = year;
    }

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult IsValid(object value,
        ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

위의 예제에서 movie 변수는 양식 제출에서 제공된 데이터를 포함하는 Movie 개체를 나타냅니다. 유효성 검사에 실패하면 오류 메시지가 발생하여 ValidationResult가 반환됩니다.

IValidatableObject

앞의 예제는 Movie 형식에 대해서만 작동합니다. 또 다른 클래스 수준 유효성 검사 방법은 다음 예제와 같이 모델 클래스 내에 IValidatableObject를 구현하는 것입니다.

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

최상위 노드 유효성 검사

최상위 수준 노드에는 다음이 포함됩니다.

  • 작업 매개 변수
  • 컨트롤러 속성
  • 페이지 처리기 매개 변수
  • 페이지 모델 속성

모델 바인딩 최상위 노드는 모델 속성의 유효성을 검사하는 것 외에도 유효성이 검사됩니다. 샘플 앱의 다음 예제에서 VerifyPhone 메서드는 RegularExpressionAttribute를 사용하여 phone 작업 매개 변수의 유효성을 검사합니다.

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

최상위 노드는 유효성 검사 특성과 함께 BindRequiredAttribute를 사용할 수 있습니다. 샘플 앱의 다음 예제에서 CheckAge 메서드는 양식을 제출할 때 쿼리 문자열에서 age 매개 변수를 바인딩해야 함을 지정합니다.

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

검사 기간 페이지(CheckAge.cshtml)에는 두 가지 양식이 있습니다. 첫 번째 양식은 99Age 값을 쿼리 문자열 매개 변수(https://localhost:5001/Users/CheckAge?Age=99)로 제출합니다.

쿼리 문자열에서 올바른 서식이 지정된 age 매개 변수를 제출하면 양식이 유효성을 검사합니다.

기간 확인 페이지의 두 번째 양식은 요청 본문의 Age 값을 제출하고 유효성 검사가 실패합니다. age 매개 변수는 쿼리 문자열에서 가져와야 하므로 바인딩이 실패합니다.

최대 오류 수

유효성 검사는 최대 오류 수(기본값 200)에 도달하면 중지됩니다. Startup.ConfigureServices에 다음 코드로 이 번호를 구성할 수 있습니다.

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

services.AddSingleton<IValidationAttributeAdapterProvider,
    CustomValidationAttributeAdapterProvider>();

최대 재귀

ValidationVisitor는 유효성 검사 중인 모델의 개체 그래프를 트래버스합니다. 깊거나 무한히 재귀하는 모델의 경우 유효성 검사를 실행하면 스택 오버플로가 발생할 수 있습니다. MvcOptions.MaxValidationDepth 는 방문자 재귀가 구성된 깊이를 초과하는 경우 유효성 검사를 일찍 중지하는 방법을 제공합니다. MvcOptions.MaxValidationDepth의 기본값은 32입니다.

자동 단락

모델 그래프에 대한 유효성 검사가 필요하지 않은 경우 유효성 검사는 자동으로 단락됩니다(건너뜀). 런타임에 유효성 검사를 건너뛰는 개체로는 기본 형식(byte[], string[], Dictionary<string, string>) 수집 및 유효성 검사기를 포함하지 않은 복합 개체 그래프가 있습니다.

클라이언트 쪽 유효성 검사

클라이언트 쪽 유효성 검사는 형식이 유효할 때까지 전송을 방지합니다. 제출 단추는 형식을 전송하거나 오류 메시지를 표시하는 JavaScript를 실행합니다.

클라이언트 쪽 유효성 검사는 양식에 대한 입력 오류가 있는 경우 불필요한 서버 왕복을 방지합니다. 다음 스크립트는 클라이언트 쪽 유효성 검사를 참조하고 _ValidationScriptsPartial.cshtml 지원합니다_Layout.cshtml.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.js"></script>

jQuery 비간섭 유효성 검사 스크립트는 널리 사용되는 jQuery 유효성 검사 플러그 인에 기반한 사용자 지정 Microsoft 프런트 엔드 라이브러리입니다. jQuery 비간섭 유효성 검사를 사용하지 않을 경우 두 위치(모델 속성에 대한 서버 쪽 유효성 검사 특성에서 한 번 및 클라이언트 쪽 스크립트에서 다시 한 번)에서 동일한 유효성 검사 논리를 코딩해야 할 수 있습니다. 그 대신 태그 도우미HTML 도우미 는 유효성 검사가 필요한 양식 요소에 대해 모델 특성의 유효성 검사 특성 및 형식 메타데이터를 사용하여 HTML 5 data- 특성을 렌더링합니다. jQuery 비간섭 유효성 검사는 data- 특성을 구문 분석하고 jQuery 유효성 검사로 논리를 전달하여 효과적으로 서버 쪽 유효성 검사 논리를 클라이언트에 “복사”합니다. 다음과 같이 태그 도우미를 사용하여 클라이언트에서 유효성 검사 오류를 표시할 수 있습니다.

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

위의 태그 도우미는 다음 HTML을 렌더링합니다.

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

HTML의 data- 특성 출력은 Movie.ReleaseDate 속성에 대한 유효성 검사 특성에 해당합니다. data-val-required 특성은 사용자가 릴리스 날짜 필드를 입력하지 않았음을 표시하는 오류 메시지를 포함합니다. jQuery 비간섭 유효성 검사는 jQuery 유효성 검사 required() 메서드에 이 값을 전달합니다. 그러면 <span> 요소와 함께 해당 메시지를 표시합니다.

데이터 형식 유효성 검사는 [DataType] 특성에 의해 재정의되지 않은 한, 속성의 .NET 형식을 기반으로 합니다. 브라우저는 고유한 기본 오류 메시지를 가지고 있지만 jQuery유효성 검사의 비간섭 유효성 검사 패키지는 해당 메시지를 재정의할 수 있습니다. [DataType] 특성 및 [EmailAddress]와 같은 서브클래스를 사용하여 오류 메시지를 지정할 수 있습니다.

비간섭 유효성 검사

비간섭 유효성 검사에 대한 자세한 내용은 이 GitHub 문제를 참조하세요.

동적 형식에 유효성 검사 추가

jQuery 비간섭 유효성 검사는 페이지가 먼저 로드되는 경우 유효성 검사 논리 및 매개 변수를 jQuery 유효성 검사에 전달합니다. 따라서 동적으로 생성된 양식에 대해서는 유효성 검사가 자동으로 작동하지 않습니다. 유효성 검사를 사용하도록 설정하려면 jQuery 비간섭 유효성 검사에서 동적 폼을 만든 후에 즉시 구문 분석하도록 지시합니다. 예를 들어 다음 코드에서는 AJAX를 통해 추가된 양식에 대해 클라이언트 쪽 유효성 검사를 설정합니다.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() 메서드는 하나의 인수에 대해 jQuery 선택기를 허용합니다. 이 메서드는 jQuery 비간섭 유효성 검사에서 해당 선택기 내에 있는 형식의 data- 특성을 구문 분석하도록 지시합니다. 그러면 이러한 특성 값이 jQuery 유효성 검사 플러그 인에 전달됩니다.

동적 컨트롤에 유효성 검사 추가

$.validator.unobtrusive.parse() 메서드는 <input><select/>와 같은 동적으로 생성된 개별 컨트롤이 아닌 전체 양식에 대해 작용합니다. 양식을 다시 구문 분석하려면 다음 예제와 같이 이전에 양식을 구문 분석했을 때 추가된 유효성 검사 데이터를 제거합니다.

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

사용자 지정 클라이언트 쪽 유효성 검사

사용자 지정 클라이언트 쪽 유효성 검사는 사용자 지정 jQuery 유효성 검사 어댑터에서 작동하는 data- HTML 특성을 생성하여 수행됩니다. 다음 샘플 어댑터 코드는 이 문서에서 이전에 소개한 [ClassicMovie][ClassicMovieWithClientValidator] 특성용으로 작성되었습니다.

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

어댑터 작성 방법에 대한 자세한 내용은 jQuery 유효성 검사 설명서를 참조하세요.

지정된 필드에 대한 어댑터 사용은 data- 특성에 의해 트리거됩니다.

  • 필드를 유효성 검사가 적용되는 것으로 표시합니다(data-val="true").
  • 유효성 검사 규칙 이름 및 오류 메시지 텍스트(예: data-val-rulename="Error message.")를 식별합니다.
  • 유효섬 검사기에 필요한 추가 매개 변수(예: data-val-rulename-param1="value")를 제공합니다.

다음 예제에서는 샘플 앱의ClassicMovie 특성에 대한 data- 특성을 보여줍니다.

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

앞에서 설명했듯이 태그 도우미HTML 도우미는 유효성 검사 특성의 정보를 사용하여 data- 특성을 렌더링합니다. 사용자 지정 data- HTML 특성을 생성하는 코드를 작성하는 두 가지 방법이 있습니다.

  • AttributeAdapterBase<TAttribute>에서 파생하는 클래스 및 IValidationAttributeAdapterProvider를 구현하는 클래스를 만들고 사용자의 특성 및 해당 어댑터를 DI에 등록합니다. 이 방법은 서버 관련 및 클라이언트 관련 유효성 검사 코드가 별도의 클래스에 있는 단일 책임 보안 주체를 따릅니다. 또한 이 어댑터는 DI에 등록되었으므로 필요한 경우 DI의 다른 서비스를 사용할 수 있다는 이점이 있습니다.
  • 사용자의 ValidationAttribute 클래스에서 IClientModelValidator를 구현합니다. 이 방법은 특성이 서버 쪽 유효성 검사를 수행하지 않고 DI의 서비스가 필요하지 않은 경우 적절할 수 있습니다.

클라이언트 쪽 유효성 검사에 대한 특성 어댑터

이 HTML data- 특성 렌더링 방법은 샘플 앱ClassicMovie 특성에 사용됩니다. 이 방법을 사용하여 클라이언트 유효성 검사를 추가하려면:

  1. 사용자 지정 유효성 검사 특성에 대한 특성 어댑터 클래스를 생성합니다. AttributeAdapterBase<TAttribute>에서 클래스를 파생합니다. 이 예제와 같이 렌더링된 출력에 data- 특성을 추가하는 AddValidation 메서드를 생성합니다.

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute,
            IStringLocalizer stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext) =>
            Attribute.GetErrorMessage();
    }
    
  2. IValidationAttributeAdapterProvider를 구현하는 어댑터 공급자 클래스를 생성합니다. GetAttributeAdapter 메서드에서 다음 예제와 같이 사용자 지정 특성을 어댑터의 생성자에 전달합니다.

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. DI용 어댑터 공급자를 Startup.ConfigureServices에 등록합니다.

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

클라이언트 쪽 유효성 검사를 위한 IClientModelValidator

이 HTML data- 특성 렌더링 방법은 샘플 앱ClassicMovieWithClientValidator 특성에 사용됩니다. 이 방법을 사용하여 클라이언트 유효성 검사를 추가하려면:

  • 사용자 지정 유효성 검사 특성에서 IClientModelValidator 인터페이스를 구현하고 AddValidation 메서드를 생성합니다. AddValidation 메서드에서 다음 예제와 같이 유효성 검사를 위한 data- 특성을 추가합니다.

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
        {
            Year = year;
        }
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult IsValid(object value,
            ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

클라이언트 쪽 유효성 검사를 사용하지 않도록 설정

다음 코드는 Razor Pages에서 클라이언트 유효성 검사를 사용하지 않도록 설정합니다.

services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

클라이언트 쪽 유효성 검사를 사용하지 않도록 설정하는 다른 옵션:

  • 모든 파일에 대한 참조를 _ValidationScriptsPartial 주석으로 처리합니다 .cshtml .
  • Pages\Shared_ValidationScriptsPartial.cshtml 파일의 콘텐츠를 제거합니다.

위의 방법으로는 ASP.NET Core IdentityRazor 클래스 라이브러리의 클라이언트 쪽 유효성 검사를 방지할 수 없습니다. 자세한 내용은 ASP.NET Core 프로젝트에서 Identity 스캐폴드를 참조하세요.

추가 리소스