简体   繁体   中英

Client Side Validation using Blazor

I have an EditForm that takes an AnimalList as a model which has a list of Animals. Im using data Annotations on the animal objects. On the front end i have the ability to display the list of animal names as a dropdown + text input combo and add objects to that list. So you would select an animal name and assign a friendly name to the animal. ie I select "Giraffe" and then give it a friendly Giraffe name like "Brian". Then clicking add more would add blank default to the list and can keep going etc. Im trying to use data annotations to get some client side validation on the text input field on each row in the sense that if i leave the friendly name blank i should get a message saying that the field is required. Preferably after ive clicked inside the text field and left (onBlur i think?).

I feel this should be standard but i feel that using a list is making this harder for standard validation.

I get validation if i enter some text, click away and then delete said text and click away, which isnt ideal. More importantly is when i click submit i feel that the form should validate and then not submit because there should be messages, but this doesnt work either and submits just fine. Ive since tried context.validate and its returns true?

Should i be looking at some sort of custom validation here or should this be do-able?

AddAnimals.Razor Code:

<Css />

<h3>Add your animals</h3>

<EditForm Class="login-form" Model="@AnimalList" OnValidSubmit="SaveAnimals">
    <DataAnnotationsValidator/>

    @if (!string.IsNullOrEmpty(Error))
    {
        <Notification Type="@NotificationType.Error" Message="@Error" />
    }

    <div class="form-group">
        <div class="form-row">
            <div class="col-6">
                <label for="Select">Select Animal</label>
            </div>
            <div class="col">
                <label>Animal Name</label>
            </div>
        </div>
        <ul>
            @foreach (var animalSelection in AnimalList.SelectedAnimals)
            {
                <li>
                    <div class="form-row">
                        <div class="col-6">
                            <select class="form-control" @bind="animalSelection.AnimalName">
                                <option value="" disabled selected hidden>Select...</option>
                                @foreach (var name in AnimalNames)
                                {
                                    <option value="@name">@name</option>
                                }
                            </select>
                        </div>
                        <div class="col">
                            <InputText  class="form-control" @bind-Value="animalSelection.FriendlyName" />
                            <ValidationMessage For="@(() => animalSelection.FriendlyName)" />
                        </div>
                    </div>
                </li>
            }
        </ul>
    </div>
    <div class="form-group">
        <button type="button" class="btn" @onclick="AddMoreAnimals">+ Add More</button>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Continue</button>
    </div>
</EditForm>

@code {

    [Parameter]
    public AnimalList AnimalList { get; set; } = new AnimalList();

    [Parameter]
    public List<string> AnimalNames { get; set; } = new List<string>();

    public string Error { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            AnimalNames = AnimalsService.GetAnimals();
            await GetAnimalList();
            if (AnimalList.SelectedAnimals.Count < 1)
            {
                AddMoreAnimals();
            }
            StateHasChanged();
        }
    }

    private async Task GetAnimalList()
    {
        AnimalList.SelectedAnimals = await AnimalsService.GetAnimals();
    }

    private void AddMoreAnimals()
    {
        AnimalList.AddDefault();
    }

    private async void SaveAnimals(EditContext context)
    {
        AnimalList.TrimList();

        foreach (var animal in AnimalsList.SelectedAnimals)
        {
            var response = await AnimalsService.AddAnimal(animal);
            if (response.IsError)
            {
                Error = response.Error;
                StateHasChanged();
                break;
            }
            else
            {
                Error = string.Empty;
            }
        }
    }
}

AnimalsList

    public class AnimalList
    {
        public List<Animal> SelectedAnimals { get; set; }

        public AnimalList()
        {
            SelectedAnimals = new List<Animal>();
        }

        public void AddDefault()
        {
            SelectedAnimals.Add(new Animal());
        }

        public void TrimList()
        {
            //Trim away possible unused rows
            SelectedAnimals.RemoveAll(
                item => string.IsNullOrEmpty(item.FriendlyName) && 
                    string.IsNullOrEmpty(item.AnimalName));
        }
    }

Animal

    public class Animal
    {
        [Required(ErrorMessage = "Animal selection is required")]
        public string AnimalName { get; set; }
        
        [Required(ErrorMessage = "You must enter a name for your Animal")]
        [DisplayFormat(ConvertEmptyStringToNull = true)]
        public string FriendlyName { get; set; }
    }

I broke this into a list of forms as I suggested.

Note: I used code behind pages.

AnimalsPage.razor

@page "/animals"

<h3>Add your animals</h3>
<CascadingValue Value="this">
    @foreach (var animal in this.animals)
    {
        <AnimalEditForm @key="animal" Animal="animal" />
    }
</CascadingValue>
<button class="btn mr-1" @onclick="AddAnimalClicked">Add Animal</button>
@if (lastValidateResult)
{
    <button class="btn btn-success" @onclick="SaveAllClicked">Save All</button>
}
else
{
    <button class="btn btn-primary" @onclick="ContinueClicked">Continue</button>
}

AnimalsPage.razor.cs

public partial class AnimalsPage
{
    private void AddAnimalClicked(MouseEventArgs mouseEventArgs)
    {
        this.animals.Add(new Animal());
        this.lastValidateResult = false;
    }

    private void ContinueClicked(MouseEventArgs mouseEventArgs)
    {
        //Your "Trim" function
        List<Animal> emptyAnimals = this.animals.Where(a => a.AnimalName == default && a.AnimalType == default).ToList();
        emptyAnimals.ForEach(a =>
        {
            this.animals.Remove(a);
            this.animalForms.Remove(a);
            this.animalValidationResults.Remove(a);
        });

        this.animalForms.Values.ToList().ForEach(a => a.ShowValidations(true));
        StateHasChanged();
    }

    private void SaveAllClicked()
    {
        //.....
    }

    internal void ChildChangedState(Animal animal, bool result)
    {
        bool prev;
        if (!this.animalValidationResults.ContainsKey(animal))
        {
            prev = false;
            this.animalValidationResults[animal] = prev;
        }
        else
        {
            prev = this.animalValidationResults[animal];
            if (prev != result)
            {
                this.animalValidationResults[animal] = result;
                this.lastValidateResult = this.animalValidationResults.Values.All(a => a);
                StateHasChanged();
            }
        }
    }
    internal Dictionary<Animal, AnimalEditForm> animalForms = new Dictionary<Animal, AnimalEditForm>();
    private readonly Dictionary<Animal, bool> animalValidationResults = new Dictionary<Animal, bool>();
    private readonly List<Animal> animals = new List<Animal>();
    private bool lastValidateResult = false;
}

AnimalEditForm.razor

<EditForm Model="Animal">
    <DataAnnotationsValidator />
    @{if (validatorVisible) { { Validate(context); } } }
    <div class="form-row mb-1">
        <div class="col">
            <InputSelect  class="form-control" @bind-Value="Animal.AnimalType">
                <option value="" selected>Select...</option>
                @foreach (var animalType in animalTypes)
                {
                    <option value=@animalType>@animalType</option>
                }
            </InputSelect>
            <ValidationMessage For="@(() => Animal.AnimalType)" />
        </div>
        <div class="col">
            <InputText class="form-control" @bind-Value="Animal.AnimalName" />
            <ValidationMessage For="@(() => Animal.AnimalName)" />
        </div>
    </div>
</EditForm>

AnimalEditForm.razor.cs

public partial class AnimalEditForm
{
    [Parameter]
    public Animal Animal { get; set; }

    [CascadingParameter]
    public AnimalsPage AnimalsPage { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            AnimalsPage.animalForms[Animal] = this;
        }

        base.OnAfterRender(firstRender);
    }
    private IReadOnlyList<string> animalTypes => new[]
    {
        "Eagle",
        "Elephant",
        "Giraffe",
        "Hippopotamus",
        "Horse",
        "Koala",
        "Zebra"
    };

    public bool LastValidateResult { get; private set; }

    private bool validatorVisible = false;

    public void ShowValidations(bool show) => this.validatorVisible = show;

    public void Validate(EditContext editContext)
    {
        LastValidateResult = editContext.Validate();
        AnimalsPage.ChildChangedState(Animal, LastValidateResult);
    }

}

在此处输入图片说明

Not use OnValidSubmit nor OnInvalidSubmit , just OnSubmit

<EditForm Class="login-form" Model="@AnimalList" OnSubmit="SaveAnimals">

In SaveAnimal, check the list

private async void SaveAnimals(EditContext context)
{
    bool valid = context.Validate();
    AnimalList.SelectedAnimals.ForEach(x =>
    {
        var field=FieldIdentifier.Create((() => x.FriendlyName));
        context.NotifyFieldChanged(field);
        if (valid)
            valid = context.GetValidationMessages(field)
                       .FirstOrDefault(x => !String.IsNullOrEmpty(x)) == null;

    });
    if (valid)
        Console.WriteLine("Save Animals");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM