Mudblazor Select 具有多选和 Fluentvalidation For-Expression

发布于 2025-01-17 18:40:31 字数 3615 浏览 1 评论 0原文

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

这是一个代码片段

当使用选择字段时,必须设置选项类型,在本例中它将是字符串。为了使验证起作用, “For”- 需要设置属性并指向与选择字段选项类型相同的有效属性(即字符串)。 但我期待多重选择,因此我绑定到模型中的 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);
        };
    }
}

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

I am binding to a select field in multiselect mode and I ran into a problem with the "For" property of the select field".

Here is a code snippet

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

漫雪独思 2025-01-24 18:40:31

mudblazor snippet。

好吧,好吧,因此,您可以通过引入多木材并结合了多个属性, - 选择该组件,然后在验证期间测试其名称。

因此当Fluent验证启动时匹配。

当表单组件将虚拟属性名称传递给验证方法时,您可以将传递的虚拟名称更改为收藏的名称,

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

Mudblazor snippet.

Ok, so you can trick the component by introducing a dummy property and binding the multi-select component to it then testing its name during validation.

When the form component passes the dummy property name to the validation method, you change the passed dummy name to the name of your collection so it's matched when fluent validation kicks in.

Something like this:

@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);
        };
    }
}
夏末的微笑 2025-01-24 18:40:31

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

@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>

Here is my solution for how to avoid it. I just created a new component.

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

Then you can use it like:

<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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文