ASP.NET Core Blazor 양식 유효성 검사

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

이 문서에서는 양식에서 Blazor 유효성 검사를 사용하는 방법을 설명합니다.

양식 유효성 검사

기본 양식 유효성 검사 시나리오에서 EditForm 인스턴스는 선언된 EditContextValidationMessageStore 인스턴스를 사용하여 양식 필드의 유효성을 검사할 수 있습니다. EditContextOnValidationRequested 이벤트 처리기는 사용자 지정 유효성 검사 논리를 실행합니다. 이 처리기의 결과가 ValidationMessageStore 인스턴스를 업데이트합니다.

기본 양식 유효성 검사는 양식의 모델이 양식을 호스트하는 구성 요소 내에서 구성 요소에서 직접 또는 하위 클래스에서 멤버로 정의되는 경우에 유용합니다. 여러 구성 요소에서 독립적인 모델 클래스를 사용하는 경우에는 유효성 검사기 구성 요소를 사용하는 것이 좋습니다.

다음 구성 요소에서 처리기 메서드는 폼의 HandleValidationRequested 유효성을 검사하기 전에 호출 ValidationMessageStore.Clear 하여 기존 유효성 검사 메시지를 지웁니다.

Starship8.razor:

@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    public Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}

데이터 주석 유효성 검사기 구성 요소 및 사용자 지정 유효성 검사

DataAnnotationsValidator 구성 요소는 데이터 주석 유효성 검사를 연계된 EditContext에 연결합니다. 데이터 주석 유효성 검사를 사용하려면 DataAnnotationsValidator 구성 요소가 필요합니다. 데이터 주석이 아닌 다른 유효성 검사 시스템을 사용하려면 DataAnnotationsValidator 구성 요소 대신 사용자 지정 구현을 사용합니다. 참조 원본에서 DataAnnotationsValidator에 대한 프레임워크 구현을 검사할 수 있습니다.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

Blazor는 다음 두 가지 유형의 유효성 검사를 수행합니다.

  • ‘필드 유효성 검사’는 사용자가 Tab 키를 눌러 필드를 벗어날 때 수행됩니다. 필드 유효성을 검사하는 동안 DataAnnotationsValidator 구성 요소는 보고된 모든 유효성 검사 결과를 필드에 연결합니다.
  • ‘모델 유효성 검사’는 사용자가 양식을 제출할 때 수행됩니다. 모델 유효성을 검사하는 동안 DataAnnotationsValidator 구성 요소는 유효성 검사 결과에 보고된 멤버 이름을 기준으로 필드를 확인합니다. 개별 멤버와 연결되지 않은 유효성 검사 결과는 필드가 아니라 모델과 연결됩니다.

유효성 검사기 구성 요소

유효성 검사기 구성 요소는 양식의 EditContext에 대한 ValidationMessageStore를 관리하여 양식 유효성 검사를 지원합니다.

Blazor 프레임워크는 유효성 검사 특성(데이터 주석)을 기반으로 양식에 유효성 검사 지원을 연결하는 DataAnnotationsValidator 구성 요소를 제공합니다. 사용자 지정 유효성 검사기 구성 요소를 만들어 양식 처리 단계(예: 클라이언트 유효성 검사 다음 서버 유효성 검사)에서 동일한 페이지 또는 동일한 양식의 다른 양식에 대한 유효성 검사 메시지를 처리할 수 있습니다. 이 섹션에 표시된 유효성 검사기 구성 요소 예제(CustomValidation)는 이 문서의 다음 섹션에서 사용됩니다.

참고 항목

대부분의 경우 사용자 지정 유효성 검사기 구성 요소 대신 사용자 지정 데이터 주석 유효성 검사 특성을 사용할 수 있습니다. 양식의 모델에 적용된 사용자 지정 특성은 DataAnnotationsValidator 구성 요소를 사용하여 활성화합니다. 서버 유효성 검사와 함께 사용할 경우 모델에 적용된 사용자 지정 특성은 서버에서 실행 가능해야 합니다. 자세한 내용은 ASP.NET Core MVC의 모델 유효성 검사를 참조하세요.

ComponentBase에서 유효성 검사기 구성 요소를 만듭니다.

  • 양식의 EditContext는 구성 요소의 연계 매개 변수입니다.
  • 유효성 검사기 구성 요소가 초기화되면 현재 양식 오류 목록을 유지하기 위해 새 ValidationMessageStore가 만들어집니다.
  • 양식의 구성 요소에 있는 개발자 코드에서 DisplayErrors 메서드를 호출하면 메시지 저장소에 오류가 수신됩니다. 오류는 Dictionary<string, List<string>>DisplayErrors 메서드에 전달됩니다. 사전에서 키는 하나 이상의 오류가 발생한 양식 필드의 이름입니다. 값은 오류 목록입니다.
  • 다음 중 어느 것이 발생했으면 메시지가 지워집니다.
    • OnValidationRequested 이벤트가 발생하면 EditContext에서 유효성 검사가 요청됩니다. 모든 오류는 지워집니다.
    • OnFieldChanged 이벤트가 발생하면 양식의 필드가 변경됩니다. 해당 필드에 대한 오류만 지워집니다.
    • ClearErrors 메서드는 개발자 코드에 의해 호출됩니다. 모든 오류는 지워집니다.

앱의 네임스페이스에 맞게 다음 클래스의 네임스페이스를 업데이트합니다.

CustomValidation.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample;

public class CustomValidation : ComponentBase
{
    private ValidationMessageStore? messageStore;

    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }

    protected override void OnInitialized()
    {
        if (CurrentEditContext is null)
        {
            throw new InvalidOperationException(
                $"{nameof(CustomValidation)} requires a cascading " +
                $"parameter of type {nameof(EditContext)}. " +
                $"For example, you can use {nameof(CustomValidation)} " +
                $"inside an {nameof(EditForm)}.");
        }

        messageStore = new(CurrentEditContext);

        CurrentEditContext.OnValidationRequested += (s, e) => 
            messageStore?.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore?.Clear(e.FieldIdentifier);
    }

    public void DisplayErrors(Dictionary<string, List<string>> errors)
    {
        if (CurrentEditContext is not null)
        {
            foreach (var err in errors)
            {
                messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
            }

            CurrentEditContext.NotifyValidationStateChanged();
        }
    }

    public void ClearErrors()
    {
        messageStore?.Clear();
        CurrentEditContext?.NotifyValidationStateChanged();
    }
}

Important

ComponentBase에서 파생할 때 네임스페이스 지정은 필수입니다. 네임스페이스를 지정하지 못하면 빌드 오류가 발생합니다.

‘ ’ 문자가 포함되어 있기 때문에 태그 도우미는 태그 이름 ‘<전역 네임스페이스>.{CLASS NAME}’을 대상으로 할 수 없습니다.

{CLASS NAME} 자리 표시자는 구성 요소 클래스의 이름입니다. 이 섹션의 사용자 지정 유효성 검사기 예제에서는 BlazorSample 네임스페이스 예제를 지정합니다.

참고 항목

익명 람다 식은 위 예제에서 OnValidationRequestedOnFieldChanged에 대해 등록된 이벤트 처리기입니다. 이 시나리오에서는 IDisposable을 구현하고 이벤트 대리자를 구독 취소할 필요가 없습니다. 자세한 내용은 ASP.NET Core Razor 구성 요소 수명 주기를 참조하세요.

유효성 검사기 구성 요소를 사용하는 비즈니스 논리 유효성 검사

일반적인 비즈니스 논리 유효성 검사는 사전에서 양식 오류를 수신하는 유효성 검사기 구성 요소를 사용할 수 있습니다.

기본 유효성 검사는 양식의 모델이 양식을 호스트하는 구성 요소 내에서 구성 요소에서 직접 또는 하위 클래스에서 멤버로 정의되는 경우에 유용합니다. 여러 구성 요소에서 독립적인 모델 클래스를 사용하는 경우에는 유효성 검사기 구성 요소를 사용하는 것이 좋습니다.

다음 예제에서

  • 입력 구성 요소 문서의 예제 양식 섹션에 있는 양식(Starship3구성 요소)단축된 버전은 Starfleet Starship Database 우주선의 분류 및 설명만 허용하는 데 사용됩니다. DataAnnotationsValidator 구성 요소가 양식에 포함되지 않기 때문에 양식 제출 시 데이터 주석 유효성 검사가 트리거되지 않습니다.
  • 이 문서의 유효성 검사기 구성 요소 섹션에서 CustomValidation 구성 요소가 사용됩니다.
  • 사용자가 ‘Defense’ 배송 분류(Classification)를 선택하는 경우 유효성 검사에는 배송 설명(Description)에 대한 값이 필요합니다.

유효성 검사 메시지가 구성 요소에 설정되어 있으면 해당 메시지가 유효성 검사기의 ValidationMessageStore에 추가되고 EditForm의 유효성 검사 요약에 표시됩니다.

Starship9.razor:

@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">
                    Select classification ...
                </option>
                <option checked="@(Model!.Classification == "Exploration")" 
                    value="Exploration">
                    Exploration
                </option>
                <option checked="@(Model!.Classification == "Diplomacy")" 
                    value="Diplomacy">
                    Diplomacy
                </option>
                <option checked="@(Model!.Classification == "Defense")" 
                    value="Defense">
                    Defense
                </option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}
@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    public Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}

참고 항목

유효성 검사 구성 요소를 사용하는 대신 데이터 주석 유효성 검사 특성을 사용할 수 있습니다. 양식의 모델에 적용된 사용자 지정 특성은 DataAnnotationsValidator 구성 요소를 사용하여 활성화합니다. 서버 유효성 검사와 함께 사용할 경우 특성은 서버에서 실행 가능해야 합니다. 자세한 내용은 ASP.NET Core MVC의 모델 유효성 검사를 참조하세요.

유효성 검사기 구성 요소를 사용하는 서버 유효성 검사

이 섹션은 웹앱 시나리오에 Blazor 중점을 두지만 웹 API에서 서버 유효성 검사를 사용하는 모든 유형의 앱에 대한 접근 방식은 동일한 일반적인 접근 방식을 채택합니다.

이 섹션은 호스트된 Blazor WebAssembly 시나리오에 중점을 두지만 웹 API에서 서버 유효성 검사를 사용하는 모든 유형의 앱에 대한 접근 방식은 동일한 일반적인 접근 방식을 채택합니다.

서버 유효성 검사는 클라이언트 유효성 검사 외에도 지원됩니다.

  • 구성 요소를 사용하여 양식에서 클라이언트 유효성 검사를 처리합니다 DataAnnotationsValidator .
  • 양식이 클라이언트 유효성 검사(OnValidSubmit 호출)를 통과하면 양식 처리를 위해 백 엔드 서버 API로 보냅니 EditContext.Model 다.
  • 서버에서 모델 유효성 검사를 처리합니다.
  • 서버 API는 개발자가 제공하는 기본 제공 프레임워크 데이터 주석 유효성 검사와 사용자 지정 유효성 검사 논리를 모두 포함합니다. 서버에서 유효성 검사를 통과하면 양식을 처리하고 성공 상태 코드(200 - OK)를 다시 보냅니다. 유효성 검사에 실패하면 실패 상태 코드(400 - Bad Request) 및 필드 유효성 검사 오류가 반환됩니다.
  • 성공 시 양식을 사용하지 않도록 설정하거나 오류를 표시합니다.

기본 유효성 검사는 양식의 모델이 양식을 호스트하는 구성 요소 내에서 구성 요소에서 직접 또는 하위 클래스에서 멤버로 정의되는 경우에 유용합니다. 여러 구성 요소에서 독립적인 모델 클래스를 사용하는 경우에는 유효성 검사기 구성 요소를 사용하는 것이 좋습니다.

다음 예제는 다음을 기반으로 합니다.

Starship 클라이언트와 서버 프로젝트 모두 모델을 사용할 수 있도록 모델(Starship.cs)을 공유 클래스 라이브러리 프로젝트에 배치합니다. 공유 앱의 네임스페이스와 일치하도록 네임스페이스를 추가하거나 업데이트합니다(예: namespace BlazorSample.Shared). 모델에는 데이터 주석이 필요하므로 공유 클래스 라이브러리가 공유 프레임워크를 사용하는지 확인하거나 공유 프로젝트에 패키지를 추가 System.ComponentModel.Annotations 합니다.

참고 항목

.NET 앱에 패키지를 추가하는 방법에 대한 지침은 패키지 사용 워크플로에서 패키지 설치 및 관리의 문서(NuGet 설명서)를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

웹앱의 Blazor 기본 프로젝트에서 별자리 유효성 검사 요청을 처리하고 실패한 유효성 검사 메시지를 반환하는 컨트롤러를 추가합니다. 공유 클래스 라이브러리 프로젝트 및 컨트롤러 클래스에 대한 마지막 using 문의 네임스페이 namespace 스를 업데이트합니다. 컨트롤러는 클라이언트 및 서버 데이터 주석 유효성 검사 외에도 사용자가 선박 분류(Classification)를 선택하는 경우 선박 설명(Description)에 대한 값이 제공되는지 확인합니다Defense.

클라이언트 앱과 서버 앱이 모두 모델을 사용할 수 있도록 Starship 모델(Starship.cs)을 솔루션의 Shared 프로젝트에 배치합니다. 공유 앱의 네임스페이스와 일치하도록 네임스페이스를 추가하거나 업데이트합니다(예: namespace BlazorSample.Shared). 모델에 데이터 주석이 필요하므로 Shared 프로젝트에 System.ComponentModel.Annotations 패키지를 추가합니다.

참고 항목

.NET 앱에 패키지를 추가하는 방법에 대한 지침은 패키지 사용 워크플로에서 패키지 설치 및 관리의 문서(NuGet 설명서)를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

Server 프로젝트에서 starship 유효성 검사 요청을 처리하고 실패한 유효성 검사 메시지를 반환하는 컨트롤러를 추가합니다. Shared 프로젝트에 대한 마지막 using 구문 및 컨트롤러 클래스에 대한 namespace에 대해 네임스페이스를 업데이트합니다. 컨트롤러는 클라이언트 및 서버 데이터 주석 유효성 검사 외에도 사용자가 선박 분류(Classification)를 선택하는 경우 선박 설명(Description)에 대한 값이 제공되는지 확인합니다Defense.

양식이 Defense 서버에 제출될 때 예정된 양식이 동일한 유효성 검사 클라이언트 쪽을 수행하지 않으므로 배송 분류에 대한 유효성 검사는 컨트롤러의 서버에서만 수행됩니다. 클라이언트 유효성 검사가 없는 서버 유효성 검사는 서버에서 사용자 입력의 프라이빗 비즈니스 논리 유효성 검사가 필요한 앱에서 일반적입니다. 예를 들어 사용자 입력의 유효성을 검사하기 위해 사용자에 대해 저장된 데이터에 있는 개인 정보가 필요할 수 있습니다. 프라이빗 데이터는 클라이언트 유효성 검사를 위해 클라이언트로 보낼 수 없습니다.

참고 항목

이 섹션의 StarshipValidation컨트롤러에서는 Microsoft Identity 2.0을 사용합니다. Web API는 이 API에 대한 ‘API.Access’ 범위를 가진 사용자에 대한 토큰만 허용합니다. API의 범위 이름이 API.Access와 다른 경우 추가 사용자 지정이 필요합니다.

보안에 대한 자세한 내용은 다음을 참조하세요.

Controllers/StarshipValidation.cs:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController : ControllerBase
{
    private readonly ILogger<StarshipValidationController> logger;

    public StarshipValidationController(
        ILogger<StarshipValidationController> logger)
    {
        this.logger = logger;
    }

    static readonly string[] scopeRequiredByApi = new[] { "API.Access" };

    [HttpPost]
    public async Task<IActionResult> Post(Starship model)
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

        try
        {
            if (model.Classification == "Defense" && 
                string.IsNullOrEmpty(model.Description))
            {
                ModelState.AddModelError(nameof(model.Description),
                    "For a 'Defense' ship " +
                    "classification, 'Description' is required.");
            }
            else
            {
                logger.LogInformation("Processing the form asynchronously");

                // async ...

                return Ok(ModelState);
            }
        }
        catch (Exception ex)
        {
            logger.LogError("Validation Error: {Message}", ex.Message);
        }

        return BadRequest(ModelState);
    }
}

앱의 컨트롤러 네임스페이스에 맞게 이전 컨트롤러(BlazorSample.Server.Controllers)의 네임스페이스를 확인하거나 업데이트합니다.

서버에서 모델 바인딩 유효성 검사 오류가 발생하는 경우 ApiController(ApiControllerAttribute)는 일반적으로 ValidationProblemDetails를 사용하여 기본 잘못된 요청 응답을 반환합니다. Starfleet Starship Database 양식의 모든 필드가 제출되지 않고 양식이 유효성 검사에 실패한 경우, 다음 예와 같이 응답에는 유효성 검사 오류보다 더 많은 데이터가 포함됩니다.

{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Id": ["The Id field is required."],
    "Classification": ["The Classification field is required."],
    "IsValidatedDesign": ["This form disallows unapproved ships."],
    "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
  }
}

참고 항목

위의 JSON 응답을 보여 주려면 양식의 클라이언트 유효성 검사를 사용하지 않도록 설정하여 빈 필드 양식 제출을 허용하거나 도구를 사용하여 Firefox Browser Developer와 같은 서버 API에 직접 요청을 보내야 합니다.

서버 API가 이전의 기본 JSON 응답을 반환하는 경우 클라이언트는 개발자 코드에서 응답을 구문 분석하여 유효성 검사 오류를 처리하기 위해 errors 노드의 자식 요소를 가져올 수 있습니다. 파일을 구문 분석하는 개발자 코드를 작성하는 것은 불편할 수 있습니다. JSON을 수동으로 구문 분석하려면 ReadFromJsonAsync을(를) 호출한 후 오류의 Dictionary<string, List<string>>을(를) 생성해야 합니다. 이상적으로 서버 API는 다음 예제와 같이 유효성 검사 오류만 반환해야 합니다.

{
  "Id": ["The Id field is required."],
  "Classification": ["The Classification field is required."],
  "IsValidatedDesign": ["This form disallows unapproved ships."],
  "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}

유효성 검사 오류만 반환하도록 서버 API의 응답을 수정하려면 파일에 주석이 추가 ApiControllerAttributeProgram 된 작업에 대해 호출되는 대리자를 변경합니다. API 엔드포인트(/StarshipValidation)의 경우 ModelStateDictionary를 사용하여 BadRequestObjectResult를 반환합니다. 다른 API 엔드포인트의 경우 새 ValidationProblemDetails로 개체 결과를 반환하여 기본 동작을 유지하세요.

웹앱의 Microsoft.AspNetCore.MvcProgram 기본 프로젝트에서 파일 맨 위에 네임스페이스를 Blazor 추가합니다.

using Microsoft.AspNetCore.Mvc;

Program 파일에서 다음 AddControllersWithViews 확장 메서드를 추가하거나 업데이트하고 다음 호출을 추가합니다ConfigureApiBehaviorOptions.

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

처음으로 웹앱의 Blazor 기본 프로젝트에 컨트롤러를 추가하는 경우 컨트롤러에 대한 서비스를 등록하는 이전 코드를 배치할 때 컨트롤러 엔드포인트를 매핑합니다. 다음 예제에서는 기본 컨트롤러 경로를 사용합니다.

app.MapDefaultControllerRoute();

참고 항목

위의 예제에서는 XSRF/CSRF(교차 사이트 요청 위조) 공격을 자동으로 완화하기 위해 호출 AddControllersWithViews 하여 컨트롤러 서비스를 명시적으로 등록합니다. 단지 사용하는 AddControllers경우 위조 방지가 자동으로 활성화되지 않습니다 .

컨트롤러 라우팅 및 유효성 검사 실패 오류 응답에 대한 자세한 내용은 다음 리소스를 참조하세요.

.Client 프로젝트에서 유효성 검사기 구성 요소 섹션에 표시된 CustomValidation 구성 요소를 추가합니다. 앱과 일치하도록 네임스페이스를 업데이트합니다(예: namespace BlazorSample.Client).

.Client 프로젝트에서 CustomValidation 구성 요소를 통해 서버 유효성 검사 오류를 표시하도록 Starfleet Starship Database 양식이 업데이트되었습니다. 서버 API에서 반환되는 유효성 검사 메시지는 CustomValidation 구성 요소의 ValidationMessageStore에 추가됩니다. 양식의 유효성 검사 요약으로 표시하기 위해 오류는 양식의 EditContext에서 사용할 수 있습니다.

다음 구성 요소에서 공유 프로젝트(@using BlazorSample.Shared)의 네임스페이스를 공유 프로젝트의 네임스페이스로 업데이트합니다. 양식에는 승인이 필요하므로 사용자가 양식으로 이동하기 위해 앱에 로그인해야 합니다.

Server 앱에서 Program 파일의 상단에 Microsoft.AspNetCore.Mvc 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Mvc;

파일에서 Program 확장 메서드를 AddControllersWithViews 찾아 다음 호출을 추가합니다 ConfigureApiBehaviorOptions.

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

참고 항목

위의 예제에서는 XSRF/CSRF(교차 사이트 요청 위조) 공격을 자동으로 완화하기 위해 호출 AddControllersWithViews 하여 컨트롤러 서비스를 명시적으로 등록합니다. 단지 사용하는 AddControllers경우 위조 방지가 자동으로 활성화되지 않습니다 .

Client 프로젝트에서 유효성 검사기 구성 요소 섹션에 표시된 CustomValidation 구성 요소를 추가합니다. 앱과 일치하도록 네임스페이스를 업데이트합니다(예: namespace BlazorSample.Client).

Client 프로젝트에서 CustomValidation 구성 요소를 통해 서버 유효성 검사 오류를 표시하도록 Starfleet Starship Database 양식이 업데이트되었습니다. 서버 API에서 반환되는 유효성 검사 메시지는 CustomValidation 구성 요소의 ValidationMessageStore에 추가됩니다. 양식의 유효성 검사 요약으로 표시하기 위해 오류는 양식의 EditContext에서 사용할 수 있습니다.

다음 구성 요소에서 프로젝트(@using BlazorSample.Shared)의 Shared 네임스페이스를 공유 프로젝트의 네임스페이스로 업데이트합니다. 양식에는 승인이 필요하므로 사용자가 양식으로 이동하기 위해 앱에 로그인해야 합니다.

Starship10.razor:

참고 항목

기본적으로 양식 기반 EditForm 의 양식은 위조 방지 지원을 자동으로 사용하도록 설정합니다. 컨트롤러는 컨트롤러 서비스를 등록하고 웹 API에 대한 위조 방지 지원을 자동으로 사용하도록 설정하는 데 사용해야 AddControllersWithViews 합니다.

@page "/starship-10"
@rendermode InteractiveWebAssembly
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm FormName="Starship10" Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

또한 웹앱 프로젝트는 .ClientBlazor 백 엔드 웹 API 컨트롤러에 HttpClient HTTP POST 요청을 등록해야 합니다. 프로젝트의 Program 파일에 다음을 .Client 확인하거나 추가합니다.

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

앞의 예제에서는 앱의 기본 주소를 가져오고 일반적으로 호스트 페이지의 태그 href 값에서 <base> 파생되는 (IWebAssemblyHostEnvironment.BaseAddress)로 기본 주소를 builder.HostEnvironment.BaseAddress 설정합니다.

@page "/starship-10"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    public Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

참고 항목

유효성 검사 구성 요소 대신 데이터 주석 유효성 검사 특성을 사용할 수 있습니다. 양식의 모델에 적용된 사용자 지정 특성은 DataAnnotationsValidator 구성 요소를 사용하여 활성화합니다. 서버 유효성 검사와 함께 사용할 경우 특성은 서버에서 실행 가능해야 합니다. 자세한 내용은 ASP.NET Core MVC의 모델 유효성 검사를 참조하세요.

참고 항목

이 섹션의 서버 유효성 검사 방법은 이 설명서 집합의 호스트된 Blazor WebAssembly 솔루션 예제에 적합합니다.

입력 이벤트를 기반으로 하는 InputText

InputText 구성 요소를 사용하여 onchange(change) 이벤트 대신 oninput(input) 이벤트를 사용하는 맞춤 구성 요소를 만듭니다. input 이벤트 트리거를 사용하면 각 키 입력에 대해 필드 유효성 검사가 수행됩니다.

다음 CustomInputText 구성 요소는 프레임워크의 InputText 구성 요소를 상속하고 이벤트 바인딩을 oninput(input) 이벤트에 설정합니다.

CustomInputText.razor:

@inherits InputText

<input @attributes="AdditionalAttributes" 
       class="@CssClass" 
       @bind="CurrentValueAsString" 
       @bind:event="oninput" />

CustomInputText 구성 요소는 InputText가 사용되는 모든 위치에서 사용할 수 있습니다. 다음 구성 요소는 공유 CustomInputText 구성 요소를 사용합니다.

Starship11.razor:

@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <CustomInputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <CustomInputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

유효성 검사 요약 및 유효성 검사 메시지 구성 요소

ValidationSummary 구성 요소는 유효성 검사 요약 태그 도우미와 유사하게, 모든 유효성 검사 메시지를 요약합니다.

<ValidationSummary />

Model 매개 변수를 사용하여 특정 모델의 유효성 검사 메시지를 출력합니다.

<ValidationSummary Model="Model" />

ValidationMessage<TValue> 구성 요소는 유효성 검사 메시지 태그 도우미와 유사하게, 특정 필드의 유효성 검사 메시지를 표시합니다. 모델 속성 이름을 지정하는 람다 식과 For 특성을 사용하여 유효성을 검사할 필드를 지정합니다.

<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />

ValidationMessage<TValue>ValidationSummary 구성 요소는 임의 특성을 지원합니다. 구성 요소 매개 변수와 일치하지 않는 특성은 생성된 <div> 또는 <ul> 요소에 추가됩니다.

앱의 스타일 시트(wwwroot/css/app.css 또는 wwwroot/css/site.css)에서 유효성 검사 메시지 스타일을 제어합니다. 기본 validation-message 클래스는 유효성 검사 메시지의 텍스트 색을 빨간색으로 설정합니다.

.validation-message {
    color: red;
}

양식 필드가 유효한지 확인

유효성 검사 메시지를 가져오지 않고 필드가 유효한지 확인하는 데 사용합니다 EditContext.IsValid .

지원되지만 권장되지 않음:

var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

권장:

var isValid = editContext.IsValid(fieldIdentifier);

사용자 지정 유효성 검사 특성

사용자 정의 유효성 검사 특성을 사용할 때 유효성 결과가 필드와 올바르게 연결되도록 하려면 ValidationResult를 만들 때 유효성 컨텍스트의 MemberName을 전달합니다.

CustomValidator.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class CustomValidator : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
        ValidationContext validationContext)
    {
        ...

        return new ValidationResult("Validation message to user.",
            new[] { validationContext.MemberName });
    }
}

ValidationContext를 통해 사용자 지정 유효성 검사 특성에 서비스를 삽입합니다. 다음 예제에서는 DI(종속성 주입)를 사용하여 사용자 입력의 유효성을 검사하는 Salad Chef 양식을 보여 줍니다.

클래스는 SaladChef 텐 포워드 샐러드에 대한 승인 된 우주선 성분 목록을 나타냅니다.

SaladChef.cs:

namespace BlazorSample;

public class SaladChef
{
    public string[] SaladToppers = { "Horva", "Kanda Root",
    "Krintar", "Plomeek", "Syto Bean" };
}

파일에서 앱의 DI 컨테이너에 등록 SaladChef 합니다 Program .

builder.Services.AddTransient<SaladChef>();

다음 SaladChefValidatorAttribute 클래스의 IsValid 메서드는 DI에서 SaladChef 서비스를 가져와 사용자의 입력을 확인합니다.

SaladChefValidatorAttribute.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorSample;

public class SaladChefValidatorAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value,
        ValidationContext validationContext)
    {
        var saladChef = validationContext.GetRequiredService<SaladChef>();

        if (saladChef.SaladToppers.Contains(value?.ToString()))
        {
            return ValidationResult.Success;
        }

        return new ValidationResult("Is that a Vulcan salad topper?! " +
            "The following toppers are available for a Ten Forward salad: " +
            string.Join(", ", saladChef.SaladToppers));
    }
}

다음 구성 요소는 ()를 샐러드 성분 문자열([SaladChefValidator]SaladIngredient)에 적용하여 SaladChefValidatorAttribute 사용자 입력의 유효성을 검사합니다.

Starship12.razor:

@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off" FormName="Starship12">
    <DataAnnotationsValidator />
    <div>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() =>
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off">
    <DataAnnotationsValidator />
    <p>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </p>
    <button type="submit">Submit</button>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() => 
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}

사용자 지정 유효성 검사 CSS 클래스 특성

사용자 지정 유효성 검사 CSS 클래스 특성은 부트스트랩과 같은 CSS 프레임워크와 통합하는 경우에 유용합니다.

사용자 정의 유효성 CSS 클래스 특성을 지정하려면 먼저 사용자 정의 유효성 검사를 위한 CSS 스타일을 제공합니다. 다음 예제에서는 유효한 (validField) 및 유효하지 않은 (invalidField) 스타일이 지정됩니다.

앱의 스타일시트에 다음 CSS 클래스를 추가합니다.

.validField {
    border-color: lawngreen;
}

.invalidField {
    background-color: tomato;
}

FieldCssClassProvider에서 파생된 클래스를 만듭니다. 이 클래스는 필드 유효성 검사 메시지를 확인하고 적절한 유효한 스타일 또는 잘못된 스타일을 적용합니다.

CustomFieldClassProvider.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        return isValid ? "validField" : "invalidField";
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        return isValid ? "validField" : "invalidField";
    }
}

SetFieldCssClassProvider를 사용하여 양식의 EditContext 인스턴스에서 CustomFieldClassProvider 클래스를 필드 CSS 클래스 제공자로 설정합니다.

Starship13.razor:

@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <InputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

@code {
    private EditContext? editContext;

    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

위의 예에서는 모든 양식 필드의 유효성을 검사하고 각 필드에 스타일을 적용합니다. 양식이 필드의 하위 세트에만 사용자 지정 스타일을 적용해야 하는 경우에는 CustomFieldClassProvider가 조건부로 스타일 적용을 적용하도록 하세요. 다음 CustomFieldClassProvider2 예제는 Name 필드에만 스타일을 적용합니다. Name이 일치하지 않는 모든 필드에서는 string.Empty가 반환되고 스타일이 적용되지 않습니다. 리플렉션을 사용하여 필드는 HTML 엔터티에 할당된 것이 아니라 id 모델 멤버의 속성 또는 필드 이름과 일치합니다.

CustomFieldClassProvider2.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = editContext.IsValid(fieldIdentifier);

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}

참고 항목

앞의 예제에서 필드 이름과 일치하는 것은 대/소문자를 구분하므로 ""로 지정된 모델 속성 멤버는 "NameName"의 조건부 검사 일치해야 합니다.

  • 올바르게 일치:fieldId.FieldName == "Name"
  • 일치하지 않습니다.fieldId.FieldName == "name"
  • 일치하지 않습니다.fieldId.FieldName == "NAME"
  • 일치하지 않습니다.fieldId.FieldName == "nAmE"

Model에 추가 속성을 추가합니다. 예를 들면 다음과 같습니다.

[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; } 

CustomValidationForm 구성 요소의 양식에 Description을 추가합니다.

<InputText @bind-Value="Model!.Description" />

구성 요소의 OnInitialized 메서드에서 EditContext 인스턴스를 업데이트하여 새 필드 CSS 클래스 공급자를 사용합니다.

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

CSS 유효성 검사 클래스는 필드에 적용되지 Description 않으므로 스타일이 지정되지 않습니다. 그러나 필드 유효성 검사는 정상적으로 실행됩니다. 문자를 10개 이상 제공하는 경우 유효성 검사 요약은 오류로 나타납니다.

설명이 너무 깁니다.

다음 예제에서

  • 사용자 지정 CSS 스타일을 Name 필드에 적용합니다.

  • 다른 모든 필드는 Blazor의 기본 논리와 유사한 논리를 적용하고 Blazor의 기본 필드 CSS 유효성 검사 스타일(modifiedvalid 또는 invalid)을 사용합니다. 기본 스타일의 경우 앱이 Blazor 프로젝트 템플릿을 기반으로 한다면 앱의 스타일 시트에 추가할 필요가 없습니다. Blazor 프로젝트 템플릿을 기반으로 하지 않는 앱의 경우 기본 스타일을 앱의 스타일 시트에 추가할 수 있습니다.

    .valid.modified:not([type=checkbox]) {
        outline: 1px solid #26b050;
    }
    
    .invalid {
        outline: 1px solid red;
    }
    

CustomFieldClassProvider3.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}

구성 요소의 OnInitialized 메서드에서 EditContext 인스턴스를 업데이트하여 이전 필드 CSS 클래스 공급자를 사용합니다.

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

CustomFieldClassProvider3사용:

  • Name 필드는 앱의 사용자 지정 유효성 검사 CSS 스타일을 사용합니다.
  • Description 필드는 Blazor의 논리 및 Blazor의 기본 필드 CSS 유효성 검사 스타일과 유사한 논리를 사용합니다.

Blazor 데이터 주석 유효성 검사 패키지

Microsoft.AspNetCore.Components.DataAnnotations.ValidationDataAnnotationsValidator 구성 요소를 사용하여 유효성 검사 환경 차이를 완화하는 패키지입니다. 이 패키지는 현재 ‘실험적’입니다.

Warning

패키지 Microsoft.AspNetCore.Components.DataAnnotations.Validation 에는 NuGet.org 최신 버전의 릴리스 후보가있습니다. 현재 실험적 릴리스 후보 패키지를 계속 사용합니다. 실험적 기능은 기능 실현 가능성을 알아보기 위해 제공되며 안정적인 버전으로 제공되지 않을 수 있습니다. 추가 업데이트는 알림 GitHub 리포지토리, dotnet/aspnetcore GitHub 리포지토리 또는 이 항목 섹션을 참조하세요.

[CompareProperty] 특성

CompareAttribute는 유효성 검사 결과를 특정 멤버에 연결하지 않으므로 DataAnnotationsValidator 구성 요소에서 제대로 작동하지 않습니다. 이로 인해 제출 시 전체 모델의 유효성을 검사하는 경우와 필드 수준 유효성 검사 간에 동작이 일치하지 않을 수 있습니다. 실험적 패키지는 Microsoft.AspNetCore.Components.DataAnnotations.Validation이러한 제한 사항을 해결하는 추가 유효성 검사 특성을 ComparePropertyAttribute도입합니다. Blazor 앱에서는 [CompareProperty][Compare] 특성을 직접 대체합니다.

중첩된 모델, 컬렉션 형식 및 복합 형식

Blazor는 기본 제공 DataAnnotationsValidator와 함께 데이터 주석을 사용하여 양식 입력의 유효성 검사를 지원합니다. 그러나 DataAnnotationsValidator는 컬렉션 형식 또는 복합 형식 속성이 아닌, 양식에 바인딩된 모델의 최상위 속성에 대해서만 유효성을 검사합니다.

컬렉션 및 복합 형식 속성을 포함하여 바인딩된 모델의 전체 개체 그래프의 유효성을 검사하려면 실험적Microsoft.AspNetCore.Components.DataAnnotations.Validation 패키지에서 제공하는 것을 사용합니다ObjectGraphDataAnnotationsValidator.

<EditForm ...>
    <ObjectGraphDataAnnotationsValidator />
    ...
</EditForm>

[ValidateComplexType]을 사용하여 모델 속성에 주석을 답니다. 다음 모델 클래스에서 ShipDescription 클래스는 모델이 양식에 바인딩된 경우 유효성을 검사할 추가 데이터 주석을 포함합니다.

Starship.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    ...

    [ValidateComplexType]
    public ShipDescription ShipDescription { get; set; } = new();

    ...
}

ShipDescription.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class ShipDescription
{
    [Required]
    [StringLength(40, ErrorMessage = "Description too long (40 char).")]
    public string? ShortDescription { get; set; }

    [Required]
    [StringLength(240, ErrorMessage = "Description too long (240 char).")]
    public string? LongDescription { get; set; }
}

양식 유효성 검사에 따라 제출 단추 사용

양식 유효성 검사를 기반으로 제출 단추를 사용하거나 사용하지 않도록 설정하려면 다음 예제를 참조하세요.

  • 입력 구성 요소 문서의 예제 양식 섹션에 있는 이전 Starfleet Starship Database 양식(Starship3구성 요소)의 단축된 버전을 사용하여 선박의 ID에 대한 값만 허용합니다. 다른 Starship 속성은 형식의 인스턴스를 만들 때 유효한 기본값을 Starship 받습니다.
  • 양식의 EditContext를 사용하여 구성 요소가 초기화될 때 모델을 할당할 수 있습니다.
  • 컨텍스트의 OnFieldChanged 콜백에서 양식의 유효성을 검사하여 제출 단추를 사용하거나 사용하지 않도록 설정합니다.
  • IDisposable을 구현하고 Dispose 메서드에서 이벤트 처리기를 구독 취소합니다. 자세한 내용은 ASP.NET Core Razor 구성 요소 수명 주기를 참조하세요.

참고 항목

EditForm.EditContext에 할당할 때 EditForm.ModelEditForm에 할당하지 마세요.

Starship14.razor:

@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
                {
                    Id = "NCC-1701",
                    Classification = "Exploration",
                    MaximumAccommodation = 150,
                    IsValidatedDesign = true,
                    ProductionDate = new DateTime(2245, 4, 11)
                };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
            {
                Id = "NCC-1701",
                Classification = "Exploration",
                MaximumAccommodation = 150,
                IsValidatedDesign = true,
                ProductionDate = new DateTime(2245, 4, 11)
            };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}

양식에 유효한 값이 미리 로드되어 있지 않고 양식 로드 시 Submit 버튼을 사용하지 않으려면 formInvalidtrue로 설정합니다.

이전 접근 방식의 부작용은 사용자가 한 필드와 상호 작용한 후 유효성 검사 요약(ValidationSummary 구성 요소)이 잘못된 필드로 채워진다는 것입니다. 다음 방법 중 하나로 이 시나리오를 처리합니다.

  • 양식에서 ValidationSummary 구성 요소를 사용하지 않습니다.
  • 제출 단추를 선택할 때 ValidationSummary 구성 요소가 표시되도록 설정합니다(예: Submit 메서드에서).
<EditForm ... EditContext="editContext" OnValidSubmit="Submit" ...>
    <DataAnnotationsValidator />
    <ValidationSummary style="@displaySummary" />

    ...

    <button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
    private string displaySummary = "display:none";

    ...

    private void Submit()
    {
        displaySummary = "display:block";
    }
}