soomon
soomon

Reputation: 437

form validation does not validate mudSelect when using FluentValidation in .net core 8

I have a class called "Dance"

public class Dance
{
    public int ID { get; set; }
    public string DisplayName { get; set; }
    public DanceType DanceType { get; set; }
    public string Letter { get; set; }
    public Motion Motion { get; set; }
    public int Level { get; set; }
    public DateTime ValidFrom { get; set; }
    public DateTime ValidUntil { get; set; }
}

which references 2 other classes:

public class DanceType
{
    public int ID { get; set; }
    public string DisplayName { get; set; }
    public DateTime ValidFrom { get; set; }
    public DateTime ValidUntil { get; set; }
}

and

public class Motion
{
    public int ID { get; set; }
    public string DisplayName { get; set; }
    public DateTime ValidFrom { get; set; }
    public DateTime ValidUntil { get; set; }
}

I am trying to set up a form with MudBlazor so the user can add a new dance, but I cannot get the validation to work. While I havent even tried to validate "DanceType", "Motion" doesnt even get validated and I dont understand why.

here is my form:

<MudForm Model="@model" @ref="@form" Validation="@(danceValidator.ValidateValue)" ValidationDelay="0">
    <MudCardContent>
        <MudTextField @bind-Value="model.DisplayName"                              
                      For="@(() => model.DisplayName)"
                      Immediate="true"
                      Label="Name" />
    </MudCardContent>
    
    <MudCardContent>
        <MudTextField @bind-Value="model.Letter"                              
                      For="@(() => model.Letter)"
                      Immediate="true"
                      Label="Letter" />
    </MudCardContent>
    
    <MudSelect @bind-Value="model.Motion"Label="Select Motion" Placeholder="Please Select">
        @foreach (Motion item in Motions)
        {
            <MudSelectItem Value="@item">@item.DisplayName</MudSelectItem>
        }
    </MudSelect>


    <MudCardContent>
        <MudTextField @bind-Value="model.Level"                              
                      For="@(() => model.Level)"
                      Immediate="true"
                      Label="Level" />
    </MudCardContent>

    <MudCardContent>
        <MudDatePicker PickerVariant="PickerVariant.Dialog" Label="Valid From" DateFormat="dd/MM/yyyy" Date="@(new System.DateTime(2020,10,19))" />
    </MudCardContent>
    <MudCardContent>
        <MudDatePicker PickerVariant="PickerVariant.Dialog" Label="Valid Until" DateFormat="dd/MM/yyyy" Date="@(new System.DateTime(2020,10,19))" />
    </MudCardContent>
    
</MudForm>

and the code

List<Motion> Motions = new List<Motion>();

MudForm form;

DanceFluentValidator danceValidator = new DanceFluentValidator();
Dance model = new Dance();


protected override async Task OnInitializedAsync()
{
    await GetAllMotions();
}

 
private async Task GetAllMotions()
{
    Motions = Dal.GetAllMotions();
}


private async Task Submit()
{
    await form.Validate();

    if (form.IsValid)
    {
        var response = Dal.AddDance(model);
        if (response.IsSuccess)
        {
            Snackbar.Add("Submitted!");
            await form.ResetAsync();
        }
        else
        {
            Snackbar.Add("Failed: " + response.ResponseText);
        }
    }
}


public class MotionFluentValidator : AbstractValidator<Motion>
{
    public MotionFluentValidator()
    {
        RuleFor(x => x.DisplayName).NotEmpty().Length(1, 99);
    }
}


public class DanceFluentValidator : AbstractValidator<Dance>
{
    public DanceFluentValidator()
    {
        RuleFor(x => x.DisplayName)
            .NotEmpty()
            .Length(1, 100);
        RuleFor(x => x.Letter)
            .NotEmpty()
            .Length(1, 1);
        RuleFor(x => x.DisplayName)
            .NotEmpty()
            .Length(1, 100);
        RuleFor(x => x.Level)
            .NotEmpty();
        RuleFor(x => x.Motion).NotNull().WithMessage("Please select a motion");
        RuleFor(x => x.ValidFrom).NotNull().WithMessage("Valid From Date is not a valid date.");
        RuleFor(x => x.ValidUntil).NotNull().WithMessage("Valid Until Date is not a valid date.");
    }

    private async Task<bool> IsUniqueAsync(string email)
    {
        // Simulates a long running http call
        await Task.Delay(2000);
        return email.ToLower() != "[email protected]";
    }

    public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
    {
        var result = await ValidateAsync(ValidationContext<Dance>.CreateWithOptions((Dance)model, x => x.IncludeProperties(propertyName)));
        
        if (result.IsValid)
            return Array.Empty<string>();
        return result.Errors.Select(e => e.ErrorMessage);
    };
}

Fore some reason, the select is not being validated at all. When stepping through "ValidateValue", I cannot see it being checked. I see all the other properties, but "Motion" doesnt show up at all.

I also tried setting up a 2nd validator and chaining the 2:

public class MotionFluentValidator : AbstractValidator<Motion>
{
    public MotionFluentValidator()
    {
        RuleFor(x => x.DisplayName).NotEmpty().Length(1, 99);
    }
    
    public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
    {
        var result = await ValidateAsync(ValidationContext<Motion>.CreateWithOptions((Motion)model, x => x.IncludeProperties(propertyName)));
        
        if (result.IsValid)
            return Array.Empty<string>();
        return result.Errors.Select(e => e.ErrorMessage);
    };
}

    public class DanceFluentValidator : AbstractValidator<Dance>
{
    public DanceFluentValidator()
    {
        RuleFor(x => x.DisplayName)
            .NotEmpty()
            .Length(1, 100);
        RuleFor(x => x.Letter)
            .NotEmpty()
            .Length(1, 1);
        RuleFor(x => x.DisplayName)
            .NotEmpty()
            .Length(1, 100);
        RuleFor(x => x.Level)
            .NotEmpty();
        RuleFor(x => x.Motion).SetValidator(new MotionFluentValidator());
        RuleFor(x => x.ValidFrom).NotNull().WithMessage("Valid From Date is not a valid date.");
        RuleFor(x => x.ValidUntil).NotNull().WithMessage("Valid Until Date is not a valid date.");
    }

but the 2nd validator doesnt get called.

Also, setting the select dropdown as it's own form with it's own model and own validator also doesnt do anything, the form for the select dropdown always returns "valid".

would anybody be able to see what I am missing?

Thank you!

Upvotes: 0

Views: 926

Answers (1)

RBee
RBee

Reputation: 4967

You're missing the For property on the components. This needs to be defined in the components where you're using validation.

<MudSelect @bind-Value="model.Motion"
            Label="Select Motion"
            For="@(() => model.Motion)" 
            Placeholder="Please Select">
    @foreach (Motion item in Motions)
    {
        <MudSelectItem Value="@item">@item.DisplayName</MudSelectItem>
    }
</MudSelect>

Same for your MudDatePickers.

<MudDatePicker PickerVariant="PickerVariant.Dialog"
    Label="Valid From"
    DateFormat="dd/MM/yyyy"
    @bind-Date="@model.ValidFrom"
    For="@(()=>model.ValidFrom)"/>

Also you were missing @bind directive for the dates. Which uses a nullable DateTime? not a DateTime type.

    public class Dance
    {
        //...
        public DateTime? ValidFrom { get; set; }
        public DateTime? ValidUntil { get; set; }
    }

Snippet

Upvotes: 1

Related Questions