KHÔNG GIAN HỌC TẬP / EditForm
Danh mục bài học Blazor Server .NET 10
BÀI 04Tương tác

EditForm

Tạo biểu mẫu có kiểm tra dữ liệu, hiển thị lỗi đúng chỗ và xử lý khi người dùng gửi thông tin.

Giải thích, ví dụ & thực hành Tất cả bài học
Thử cả dữ liệu đúng và dữ liệu sai

Gửi một form trống để xem thông báo lỗi, sau đó điền đủ thông tin và gửi lại. Mỗi ví dụ có trạng thái và nút đặt lại riêng để bạn dễ đối chiếu.

01 · EditForm cơ bản và thông báo lỗi

Status: none

Xem code
@page "/editform-demo"

<EditForm Model="@BasicModel"
          OnValidSubmit="Basic_OnValidSubmit"
          OnInvalidSubmit="Basic_OnInvalidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText class="form-control" @bind-Value="BasicModel.Name" />
    <ValidationMessage For="@(() => BasicModel.Name)" />

    <InputText class="form-control" @bind-Value="BasicModel.Email" />
    <ValidationMessage For="@(() => BasicModel.Email)" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private BasicUser BasicModel = new();
    private string BasicStatus = "none";

    private void Basic_OnValidSubmit()
        => BasicStatus = $"✅ Valid: {BasicModel.Name} - {BasicModel.Email}";

    private void Basic_OnInvalidSubmit()
        => BasicStatus = "❌ Invalid submit";

    private void Basic_Reset()
    {
        BasicModel = new();
        BasicStatus = "reset";
    }

    public class BasicUser
    {
        [Required, StringLength(30)]
        public string Name { get; set; } = "";

        [Required, EmailAddress]
        public string Email { get; set; } = "";
    }
}

02 · Các loại input và kết quả gửi form

Status: none

Xem code

<EditForm Model="@FullModel"
          OnValidSubmit="Full_OnValidSubmit"
          OnInvalidSubmit="Full_OnInvalidSubmit">

    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText class="form-control" @bind-Value="FullModel.Username" />
    <ValidationMessage For="@(() => FullModel.Username)" />

    <InputNumber class="form-control" @bind-Value="FullModel.Age" />
    <ValidationMessage For="@(() => FullModel.Age)" />

    <InputSelect class="form-select" @bind-Value="FullModel.Role">...</InputSelect>
    <ValidationMessage For="@(() => FullModel.Role)" />

    <InputDate class="form-control" @bind-Value="FullModel.Birthday" />
    <ValidationMessage For="@(() => FullModel.Birthday)" />

    <InputText type="password" class="form-control" @bind-Value="FullModel.Password" />
    <ValidationMessage For="@(() => FullModel.Password)" />

    <InputText type="password" class="form-control" @bind-Value="FullModel.ConfirmPassword" />
    <ValidationMessage For="@(() => FullModel.ConfirmPassword)" />

    <InputCheckbox class="form-check-input" @bind-Value="FullModel.Accepted" />
    <ValidationMessage For="@(() => FullModel.Accepted)" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private FullUser FullModel = new();
    private string FullStatus = "none";

    private void Full_OnValidSubmit() => FullStatus = "✅ Valid submit";
    private void Full_OnInvalidSubmit() => FullStatus = "❌ Invalid submit";

    private void Full_Reset()
    {
        FullModel = new();
        FullStatus = "reset";
    }

    public class FullUser
    {
        [Required, StringLength(20, MinimumLength = 3)]
        public string Username { get; set; } = "";

        [Range(18, 60)]
        public int Age { get; set; } = 18;

        [Required]
        public string Role { get; set; } = "";

        [Required]
        public DateTime? Birthday { get; set; } = DateTime.Today;

        [Required, MinLength(6)]
        public string Password { get; set; } = "";

        [Compare(nameof(Password), ErrorMessage = "Passwords do not match")]
        public string ConfirmPassword { get; set; } = "";

        [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept terms")]
        public bool Accepted { get; set; }
    }
}

03 · EditContext — theo dõi trường đã thay đổi

Dirty fields:none

Status: none

Xem code

<EditForm EditContext="@TrackContext" OnSubmit="Track_OnSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText class="form-control" @bind-Value="TrackModel.Title" />
    <ValidationMessage For="@(() => TrackModel.Title)" />

    <InputNumber class="form-control" @bind-Value="TrackModel.Quantity" />
    <ValidationMessage For="@(() => TrackModel.Quantity)" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private TrackItem TrackModel = new();
    private EditContext TrackContext = default!;
    private HashSet<string> DirtyFields = new();
    private string TrackStatus = "none";

    protected override void OnInitialized()
    {
        TrackContext = new EditContext(TrackModel);
        TrackContext.OnFieldChanged += Track_OnFieldChanged;
    }

    private void Track_OnFieldChanged(object? sender, FieldChangedEventArgs e)
        => DirtyFields.Add(e.FieldIdentifier.FieldName);

    private void Track_OnSubmit()
    {
        var ok = TrackContext.Validate(); // validate thủ công
        TrackStatus = ok ? "✅ Valid" : "❌ Invalid";
    }

    private void Track_Reset()
    {
        DirtyFields.Clear();
        TrackStatus = "reset";

        TrackContext.OnFieldChanged -= Track_OnFieldChanged;
        TrackModel = new TrackItem();
        TrackContext = new EditContext(TrackModel);
        TrackContext.OnFieldChanged += Track_OnFieldChanged;
    }

    public class TrackItem
    {
        [Required]
        public string Title { get; set; } = "";

        [Range(1, 999)]
        public int Quantity { get; set; } = 1;
    }
}

04 · Kiểm tra riêng bằng ValidationMessageStore

Status: none

Demo: nhập admin hoặc root sẽ báo “đã tồn tại”.

Xem code

<EditForm EditContext="@CustomContext" OnSubmit="Custom_OnSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText class="form-control" @bind-Value="CustomModel.Username" />
    <ValidationMessage For="@(() => CustomModel.Username)" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private CustomUser CustomModel = new();
    private EditContext CustomContext = default!;
    private ValidationMessageStore CustomStore = default!;
    private string CustomStatus = "none";

    protected override void OnInitialized()
    {
        CustomContext = new EditContext(CustomModel);
        CustomStore = new ValidationMessageStore(CustomContext);
    }

    private void Custom_OnSubmit()
    {
        // 1) Clear lỗi custom cũ
        CustomStore.Clear();

        // 2) Validate theo DataAnnotations
        var ok = CustomContext.Validate();

        // 3) Rule custom: username bị trùng
        var taken = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        { "admin", "root" };

        if (!string.IsNullOrWhiteSpace(CustomModel.Username) && taken.Contains(CustomModel.Username))
        {
            ok = false;

            CustomStore.Add(
                CustomContext.Field(nameof(CustomModel.Username)),
                "Username already exists."
            );

            // báo Blazor cập nhật validation UI
            CustomContext.NotifyValidationStateChanged();
        }

        CustomStatus = ok ? "✅ Valid" : "❌ Invalid";
    }

    private void Custom_Reset()
    {
        CustomModel = new CustomUser();
        CustomStatus = "reset";
        CustomContext = new EditContext(CustomModel);
        CustomStore = new ValidationMessageStore(CustomContext);
    }

    public class CustomUser
    {
        [Required, StringLength(20, MinimumLength = 3)]
        public string Username { get; set; } = "";
    }
}
Bài tiếp: Lifecycle
Web hosting by Somee.com