I am binding to a select field in multiselect mode and I ran into a problem with the "For" property of the select field".
When using a select field an options type must be set and in this example it will be string. To make validation work the "For"-Property needs to be set and pointing to a valid property of the same type as the select fields option (and thats string). But I am expecting a multiselect, so I am binding to an IEnumerable<string> in my model and the validation code is also set for this property. I don`t have the necessary property to bind to and even if I did, the validation would not work as expected.
How do I make this work? I tried building a custom expression which would point to the first element of the array, but I am bad with expressions and couldn`t make it work.
@using FluentValidation
<MudCard>
<MudForm Model="@model" @ref="@form" Validation="@(testValidator.ValidateValue)" ValidationDelay="0">
<MudCardContent>
<MudSelect T="string" Label="Name"
HelperText="Pick your favorite name" MultiSelection="false" @bind-Value="model.Name" For="() => model.Name">
@foreach (var name in _names)
{
<MudSelectItem T="string" Value="@name">@name</MudSelectItem>
}
</MudSelect>
<MudSelect T="string" Label="Names"
HelperText="Pick your favorite names" MultiSelection="true" @bind-SelectedValues="model.Names"
@* For="() => model.Names" This needs to be set to make validation work *@
>
@foreach (var name in _names)
{
<MudSelectItem T="string" Value="@name">@name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudForm>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="@(async () => await Submit())">Order</MudButton>
</MudCardActions>
</MudCard>
@code {
[Inject] ISnackbar Snackbar { get; set; }
private string[] _names = new string[] {
"Toni", "Matthew", "David"
};
MudForm form;
TestModelFluentValidator testValidator = new TestModelFluentValidator();
TestModel model = new TestModel();
public class TestModel
{
public string Name { get; set; }
public IEnumerable<string> Names { get; set; }
}
private async Task Submit()
{
await form.Validate();
if (form.IsValid)
{
Snackbar.Add("Submited!");
}
}
/// <summary>
/// A standard AbstractValidator which contains multiple rules and can be shared with the back end API
/// </summary>
/// <typeparam name="OrderModel"></typeparam>
public class TestModelFluentValidator : AbstractValidator<TestModel>
{
public TestModelFluentValidator()
{
RuleFor(x => x.Name)
.NotEmpty();
RuleFor(x => x.Names).Must((parent, property) => property.Contains("Toni"))
.WithMessage("Toni not found in those names!");
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<TestModel>.CreateWithOptions((TestModel)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
}
Edit: Added code sample and trimmed unecessary code.