Search code examples
c#validationblazor-webassembly

Blazor EditContext validation happening when I don't want it to


This is probably a simple goof, but can't figure it out.

I have a Blazor form with a few input controls mapped to an object (FormFieldsModel) mapped to an edit context. I have an Add button that uses those fields to add the data to a grid that uses a collection of those objects. Then another Submit button to send the whole collection to the API.

The desired flow would be, any time the user presses Add, validation is run against the edit context form fields. If they are invalid, display any messages. If it is valid, add the item to the collection (see OnAddClicked), and reset the form, but retaining the SubmitterInitials data (see ClearInput). They can then either submit the collection of items, or add another item.

What is happening is, if there are no items yet added to the collection, my Validation works correctly if the user presses Add. If they enter the data correctly, the item is added to the list, a new form object is created, but validation messages display.

<div>
<EditForm EditContext="@editContext">
    <DataAnnotationsValidator />
    <ValidationSummary />
    ....
</EditForm>
....
@code{
    ObservableCollection<FormFieldsModel> items;
    FormFieldsModel? formFields;
    EditContext exitContext;
    protected override async Task OnInitializedAsync()
    {
        formFields = new FormFieldsModel();
        editContext = new EditContext(formFields);
        validationMessageStore = new(editContext);        
    }
    public void OnAddClicked()
    {
        validationMessageStore.Clear();
        if (!editContext.Validate()) return;
        items.Add(formFields);
        ClearInput();
    }
    public void ClearInput()
    {
        formFields = new FormFieldsModel
        {
            // I want this to persist for any additional records
            SubmitterInitials = formFields.SubmitterInitials 
        };
        editContext = new EditContext(formFields);
        validationMessageStore.Clear();
        editContext.NotifyValidationStateChanged();
    }
    public class FormFieldsModel
    {
        public string ItemNumber {get; set;}
        public int Quantity {get; set;
        public string SubmitterInitials {get; set;}
    }
}

Solution

  • Here's a working version of your page.

    I've:

    1. Fixed various code issues.
    2. Removed the ValidationMessageStore code. ValidationMessageStore is compartmentalised: you can't clear other component's entries in the store, only read them.
    3. Created code to rebuild the form on successful submit. You need to do this to reset EditForm correctly.
    @page "/"
    @using System.ComponentModel.DataAnnotations
    
    <PageTitle>Index</PageTitle>
    
    @if (!_resetForm)
    {
        <EditForm EditContext="@editContext">
            <DataAnnotationsValidator />
            <div class="col mb-3">
                <label>Item No:</label>
                <InputText class="form-control" @bind-Value=formFields.ItemNumber />
                <ValidationMessage For="() => formFields.ItemNumber" />
            </div>
            <div class="col mb-3">
                <label>Quantity:</label>
                <InputNumber class="form-control" @bind-Value=formFields.Quantity />
                <ValidationMessage For="() => formFields.Quantity" />
            </div>
            <div class="col mb-3">
                <label>Submitter Initials:</label>
                <InputText class="form-control" @bind-Value=formFields.SubmitterInitials />
                <ValidationMessage For="() => formFields.SubmitterInitials" />
            </div>
            <div class="col mb-3 text-end">
                <button class="btn btn-primary" @onclick=this.OnAddClicked>Submit</button>
            </div>
        </EditForm>
    }
    
    <div class="bg-dark text-white p-3 m-3">
        @foreach (var item in items)
        {
            <pre>Item: @(item.ItemNumber) - Qty : @item.Quantity - Initials : @item.SubmitterInitials </pre>
        }
    </div>
    
    @code {
        List<FormFieldsModel> items = new();
        FormFieldsModel formFields = new();
        EditContext editContext = default!;
        bool _resetForm;
    
        protected override void OnInitialized()
        {
            formFields = new FormFieldsModel();
            editContext = new EditContext(formFields);
            ArgumentNullException.ThrowIfNull(editContext);
        }
    
        public async Task OnAddClicked()
        {
            if (!editContext.Validate())
                return;
    
            items.Add(formFields);
            // Set reset form and yield so Renderer renders the page with no form
            _resetForm = true;
            await Task.Delay(1);
            // Set back so at the end of the handler the Renderer will render the page with a new form
            _resetForm = false;
            ClearInput();
        }
    
        public void ClearInput()
        {
            formFields = new FormFieldsModel
                {
                    // I want this to persist for any additional records
                    SubmitterInitials = formFields.SubmitterInitials
                };
            editContext = new EditContext(formFields);
        }
    
        public class FormFieldsModel
        {
            [Required]
            [StringLength(5)]
            public string? ItemNumber { get; set; }
    
            public int Quantity { get; set; }
    
            [Required]
            [StringLength(3)]
            public string? SubmitterInitials { get; set; }
        }
    }