如何在 Blazor 中覆盖 InputBase<T> 值,以进行验证

Posted

技术标签:

【中文标题】如何在 Blazor 中覆盖 InputBase<T> 值,以进行验证【英文标题】:How to override InputBase<T> Value in Blazor, in order to validate 【发布时间】:2020-12-21 09:45:40 【问题描述】:

我正在尝试构建一个新的 Blazor 输入组件,它继承自 InputBase,以便我可以提供一个表单输入字段来选择组织中的员工。我需要提供将输入限制为单个人或允许多项选择的功能,具体取决于用例。

我正在构建的输入组件接受“员工”类型的列表,这是一个专门为我们的员工数据集构建的类。我已经设置了一个布尔值来表示是否应该允许多选。但是,如果应该将其限制为单个选择,我无法拦截 Value 的更改以阻止添加其他人。

我已经尝试过以下对 InputBase Value 属性的覆盖:


    new public List<Employee> Value
    
        get
        
            return CurrentValue;
        
        set
        
            if (!Multiselect)
            
                CurrentValue = value.Take(1).ToList();
            
            else
            
                CurrentValue = value;
            
        
    

由于有一个 List 绑定,我已经覆盖了 FormatValueAsString 和 TryParseValueFromString 以便可以在输入字段中看到电子邮件地址,作为组件的一部分。我可以将多选逻辑添加到这些函数中,但是它们仍然允许 Value 以多个 in 结尾,尽管 UI 中显示的字符串只会包含一个。

编辑: 根据要求添加更多代码。

.razor 文件:

@inherits InputBase<List<Employee>>
@using Project.Models.Employee

<div class="InputEmployee">
    <input class="InputEmployee" @bind="CurrentValueAsString" type="text" />
    <i class="oi oi-person" @onclick="() => openSearch()"></i>
</div>
<div class="InputEmployeeSearch @(displaySearch ? "": " collapse")" @onblur="() => toggleSearch()" id="searchInput">
    <div class="row">
        <div class="searchInput">
            <input @bind-Value="searchString" @bind-Value:event="oninput" placeholder="Search by name, email address, employee number" type="text" />
        </div>
    </div>
    @if (activeSearch)
    
        <div class="row">
            <div class="searchActive">
                Searching...
            </div>
        </div>
    
    @if (foundEmployees != null && foundEmployees.Any())
    
        <div class="row">
            <div class="searchResults">
                @foreach (Employee employee in foundEmployees)
                
                    <div class="employeeResult" @onclick="() => select(employee)">
                        @employee.DisplayName
                    </div>
                
            </div>
        </div>
    
    @if (errorMessage != null)
    
        <div class="row">
            <div class="searchError">
                @errorMessage
            </div>
        </div>
    
</div>

.razor.cs 文件:

using Project.Models.Employee;
using Project.Services.Employee;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;

namespace Project.Components

    public partial class InputEmployee : InputBase<List<Employee>>
    
        [Inject]
        private IEmployeeDataService EmployeeDataService  get; set; 

        [Parameter]
        public int Debounce  get; set;  = 500;
        [Parameter]
        public int MaximumResults  get; set;  = 10;
        [Parameter]
        public int MinimumLength  get; set;  = 4;
        [Parameter]
        public bool Multiselect  get; set;  = false;
        new public List<Employee> Value
        
            get
            
                return CurrentValue;
            
            set
            
                if (!Multiselect)
                
                    CurrentValue = value.Take(1).ToList();
                
                else
                
                    CurrentValue = value;
                
            
        

        private string currentValueOverlay = "";
        private bool activeSearch = false;
        private bool displaySearch = false;
        private Timer debounceTimer;
        private string searchString
        
            get
            
                return searchText;
            

            set
            
                searchText = value;

                if (value.Length == 0)
                
                    debounceTimer.Stop();
                    activeSearch = false;
                
                else
                
                    debounceTimer.Stop();
                    debounceTimer.Start();
                    activeSearch = true;
                
            
        
        private string searchText = "";
        private string errorMessage;
        private List<Employee> foundEmployees;

        protected override void OnParametersSet()
        
            base.OnParametersSet();
            debounceTimer = new Timer();
            debounceTimer.Interval = Debounce;
            debounceTimer.AutoReset = false;
            debounceTimer.Elapsed += search;
        

        protected override bool TryParseValueFromString(string value, out List<Employee> result, out string validationErrorMessage)
        
            result = new List<Employee>();
            validationErrorMessage = null;

            string[] valueArray = value.Split(";");
            List<Employee> output = new List<Employee>();

            foreach (string employeeEmail in valueArray)
            
                try
                
                    Employee employee = Task.Run(async () => await EmployeeDataService.FindEmployeesAsync(employeeEmail.Trim())).Result.FirstOrDefault();

                    if (employee != null)
                    
                        output.Add(employee);
                    
                
                catch
                
                    validationErrorMessage = $"User \"employeeEmail\" was not found.";
                
            

            result = output;
            return true;
        

        protected override string FormatValueAsString(List<Employee> employees)
        
            string employeeString = "";

            if (employees.Any() && employees.FirstOrDefault() != null)
            
                employeeString = String.Join("; ", employees.Select(x => x.Email).ToArray());
            

            return employeeString;
        

        private async void toggleSearch()
        
            displaySearch = !displaySearch;

            await InvokeAsync(StateHasChanged);
        

        private void openSearch()
        
            foundEmployees = new List<Employee>();

            if (searchString.Length >= MinimumLength)
            
                search(null, null);
            

            toggleSearch();
        

#nullable enable
        private async void search(Object? source, ElapsedEventArgs? e)
        
            errorMessage = null;
            foundEmployees = null;
            await InvokeAsync(StateHasChanged);

            if (int.TryParse(searchString, out int i))
            
                if (searchString.Length == 9)
                
                    List<string> searchList = new List<string>();
                    searchList.Add(searchString);
                    foundEmployees = (await EmployeeDataService.GetEmployeesAsync(searchList)).ToList();
                
                else
                
                    updateError($"Searching by employee number requires the full number.");
                
            
            else
            
                if (searchString.Length < MinimumLength)
                
                    updateError($"You must enter at least MinimumLength characters of their name or email address.");
                
                else
                
                    foundEmployees = (await EmployeeDataService.FindEmployeesAsync(searchString)).Take(MaximumResults).ToList();
                
            

            if (foundEmployees != null && !foundEmployees.Any())
            
                updateError($"No employees found matching \"searchString\".");
            

            activeSearch = false;
            await InvokeAsync(StateHasChanged);
        

        private async void updateError(string message)
        
            activeSearch = false;
            errorMessage = message;
            await InvokeAsync(StateHasChanged);
        

        private void select(Employee employee)
        
            Value.Add(employee);
        
    

【问题讨论】:

您介意共享自定义输入控件的代码及其 html 吗? 目前我发现的唯一解决方法是将 InputBase.cs 的源代码复制到自定义类 InputBaseVirtual.cs 中,然后将 Value 属性设为虚拟。稍后,从这个其他类继承。 【参考方案1】:

在我看来你想限制这种方法

private void select(Employee employee)

  if (MultiSelect)
  
    Value.Add(employee);
  
  else
   
    Value = new List<Employee>  employee ;
  

【讨论】:

我已经考虑过这一点,但是我需要将验证添加到与 Value 交互的任何地方,例如在字符串转换方法中。必须有一种方法可以跟踪 Value(我认为这是组件数据存储的最低级别)并在那里进行验证。【参考方案2】:

要在自定义控件中添加验证消息,您可以覆盖 InputBase 上的 TryParseValueFromString 方法并编写如下:

protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage)

    if (typeof(TValue) == typeof(string))
    
        result = (TValue)(object)value;
        validationErrorMessage = null;
        return true;
    
    else if (typeof(TValue).IsEnum)
    
        var success = BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var parsedValue);
        if (success)
        
            result = parsedValue;
            validationErrorMessage = null;
            return true;
        
        else
        
            result = default;
            validationErrorMessage = $"The FieldIdentifier.FieldName field is not valid.";
            return false;
        
    

    throw new InvalidOperationException($"GetType() does not support the type 'typeof(TValue)'.");

这取自 Chris Sainty 的 this blog 并提供了更多详细信息。

【讨论】:

将我的逻辑添加到这些方法中,确实在文本框中显示了正确的值。但是,输入组件的基础值仍然包含许多员工,这些员工将被保存回数据库。我需要管理组件 Value 以确保它始终正确。

以上是关于如何在 Blazor 中覆盖 InputBase<T> 值,以进行验证的主要内容,如果未能解决你的问题,请参考以下文章

Blazor 输入掩码

双向绑定和后台更新不重新渲染组件

如何更改从 inputBase 继承的选择组件的 CSS、概述及其伪类

React Material中InputBase中的边框颜色

有没有办法在不覆盖现有类名的情况下将类名添加到 blazor 组件?

如何同时从不同的 Blazor 组件进行数据调用?