Mudblazor Select 具有多选功能和 Fluentvalidation For-Expression

问题描述 投票:0回答:2

我在多选模式下绑定到选择字段,并且遇到了选择字段的“For”属性的问题。

这是一个代码片段

使用选择字段时,必须设置选项类型,在本例中为string。为了使验证工作,需要设置“For”属性并指向与选择字段选项类型相同的有效属性(即string)。 但我期待多重选择,因此我绑定到模型中的 IEnumerable,并且还为此属性设置了验证代码。 我没有必要的属性来绑定,即使我这样做了,验证也不会按预期工作。

我该如何进行这项工作?我尝试构建一个自定义表达式,该表达式将指向数组的第一个元素,但我对表达式很不好,无法使其工作。

@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);
        };
    }
}

编辑:添加了代码示例并删除了不必要的代码。

.net-core fluentvalidation mudblazor
2个回答
2
投票

Mudblazor 片段。

好的,所以您可以通过引入虚拟属性并将多选组件绑定到它,然后在验证期间测试其名称来欺骗该组件。

当表单组件将虚拟属性名称传递给验证方法时,您可以将传递的虚拟名称更改为集合的名称,以便在流畅验证启动时进行匹配。

类似这样的:

@using FluentValidation
@using System.Reflection

<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-Value="model.NameCollection" @bind-SelectedValues="model.Names"
                    For="@(() => model.NameCollection)"
                     >
                        @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 string NameCollection { 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!");
        }

        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) =>
        {        
            propertyName = propertyName == nameof(TestModel.NameCollection) ? nameof(TestModel.Names) : 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);
        };
    }
}

0
投票

这是我如何避免它的解决方案。我刚刚创建了一个新组件。

@using System.Linq.Expressions

@typeparam T

<MudSelect 
    T="T"
    SelectedValuesChanged="SelectedValuesChangedHandlerAsync"
    MultiSelection="true"
    ToStringFunc="@ToStringFunc"
    Error="@(_validationError is not null)"
    ErrorText="@_validationError"
    Label="@Label">
    @foreach (var value in Items)
    {
        <MudSelectItem Value="value">
            @ItemTemplate?.Invoke(value)
        </MudSelectItem>
    }
</MudSelect>
@code {

    private string? _validationError;
    private FieldIdentifier? _fieldIdentifier;
    
    [CascadingParameter]
    public EditContext? EditContext { get; set; }
    
    [Parameter]
    public IEnumerable<T> Values { get; set; } = [];
    
    [Parameter]
    public EventCallback<IEnumerable<T>> ValuesChanged { get; set; }
    
    [Parameter]
    public Expression<Func<IEnumerable<T>>>? For { get; set; }
    
    [Parameter]
    public RenderFragment<T>? ItemTemplate { get; set; }

    [Parameter]
    public IEnumerable<T> Items { get; set; } = [];
    
    [Parameter]
    public Func<T, string>? ToStringFunc { get; set; }
    
    [Parameter]
    public string? Label { get; set; }

    protected override void OnParametersSet()
    {
        if (For is not null)
        {
            _fieldIdentifier = FieldIdentifier.Create(For);
        }
    }

    private async Task SelectedValuesChangedHandlerAsync(IEnumerable<T> values)
    {
        var valuesCollection = values as ICollection<T> ?? values.ToArray();
        
        Values = valuesCollection;
        await ValuesChanged.InvokeAsync(valuesCollection);

        if (_fieldIdentifier is not null)
        {
            EditContext?.NotifyFieldChanged(_fieldIdentifier.Value);

            _validationError = EditContext?.GetValidationMessages(_fieldIdentifier.Value).FirstOrDefault();
        }
    }
}

然后你可以像这样使用它:

<MultiSelect                               
    T="AvailableDatesForGroupResponseDto.DateRangeDto"
    @bind-Values="@Model.SelectedValues"
    Items="@Model.Items"
    ToStringFunc="@(x => x.SomeData)"
    For="@(() => Model.SelectedValues)"
    Label="Your label">
            <ItemTemplate>
                    @context.SomeData
            </ItemTemplate>
    </MultiSelect>
© www.soinside.com 2019 - 2024. All rights reserved.