验证 2 个组件具有相同的值

Validate 2 components have the same value

我写了一个由 4 个输入字段组成的 PIN 组件(因为它将在几个地方重复使用)

<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinOne"/>
<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinTwo"/>
<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinThree"/>
<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinFour" @oninput="Completion"/>

@code{
    [Parameter]
    public EventCallback<string> Completed { get; set; }

    private string _pinOne;
    private string _pinTwo;
    private string _pinThree;
    private string _pinFour;

    private void Completion(ChangeEventArgs e)
    {
        _pinFour = e.Value.ToString();

        Completed.InvokeAsync(_pinOne + _pinTwo + _pinThree + _pinFour);
    }
}

然后我创建了另一个使用这些 PIN 输入组件中的 2 个的组件

<PinComponent Completed="@PinCompleted"></PinComponent>
<PinComponent Completed="@ConfirmationPinCompleted"></PinComponent>
@code {
    private string _pin;
    private string _confirmationPin;

    private bool _valid = false;

    private void PinCompleted(string pin)
    {
        _pin = pin;
    }

    private void ConfirmationPinCompleted(string pin)
    {
        _confirmationPin = pin;

        if (_pin.Equals(_confirmationPin))
        {
            _valid = true;
        }
    }
}

是否可以使用 Blazor 的 ValidationMessage 来确保这两个组件共享相同的值?

将值和验证结果传递给您的 PinComponent 并使该组件显示验证错误。

<PinComponent Completed="@PinCompleted"></PinComponent>
<PinComponent Completed="@ConfirmationPinCompleted" ValidationMessage="@validationMessage"></PinComponent>

@code {
    private string _pin;
    private string _confirmationPin;

    private bool _valid = false;

    private string ValidationMessage => _valid ? string.Empty : "PIN does not match";

    private void PinCompleted(string pin)
    {
        _pin = pin;
    }

    private void ConfirmationPinCompleted(string pin)
    {
        _confirmationPin = pin;

        if (_pin.Equals(_confirmationPin))
        {
            _valid = true;
        }
    }
}

如果您想使用 Blazor Forms 验证

class PinModel
{
    [Required]
    public string Pin {get;set;}

    [Required]
    [PinTheSame]
    public string PinConfirmation {get;set;}
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class PinTheSameAttirbute: ValidationAttribute
{

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
        if (value == null) return new ValidationResult("A pin is required.");

        // Make sure you change PinModel to whatever  the actual name is
        if ((validationContext.ObjectType.Name != "PinModel") 
             return new ValidationResult("This attribute is being used incorrectly.");
        if (((PinModel)validationContext.ObjectInstance).ConfirmPin != value.ToString())
            return new ValidationResult("Pins must match.");

        return ValidationResult.Success;
        }

}

并将值作为模型传递

<EditForm Model="@Model">
    <PinComponent Value="@Pin"></PinComponent>
    <PinComponent Value="@ConfirmationPin"></PinComponent>
</EditForm>

最后一种方法未完全完成,但应该让您了解方向。

好吧,我决定使用 FluentValidation,因为出于某种原因我无法让自定义属性(或内置 Compare 属性)正常工作

PinComponent.Razor

<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinOne"/>
<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinTwo"/>
<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinThree"/>
<input class="pinBox" type="password" maxlength="1" size="1" onkeypress='return event.charCode >= 48 && event.charCode <= 57' required @bind="@_pinFour" @oninput="Completion"/>

@code{
    private string _value;
    [Parameter]
    public string Value
    {
        get { return Value; }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                _pinOne = null;
                _pinTwo = null;
                _pinThree = null;
                _pinFour = null;
            }

            _value = value;
        }
    }

    [Parameter]
    public EventCallback<string> ValueChanged { get; set; }

    private string _pinOne;
    private string _pinTwo;
    private string _pinThree;
    private string _pinFour;

    private void Completion(ChangeEventArgs e)
    {
        _pinFour = e.Value.ToString();

        ValueChanged.InvokeAsync(_pinOne + _pinTwo + _pinThree + _pinFour);
    }
}

PinConfirmationComponent.Razor

@using Application.Validation

<EditForm Model="@_model" OnValidSubmit="@OnValidSubmit" OnInvalidSubmit="@OnInvalidSubmit">
    <div class="pinContainer">
        <PinComponent @bind-Value="_model.Pin"></PinComponent>
        <PinComponent @bind-Value="_model.PinConfirmation"></PinComponent>
    </div>
    <FluentValidationValidator />
    <ValidationSummary />
    <input id="btnSubmit" class="btn btnFont" type="submit" value="Register PIN" style="margin-top: 5px;" />
</EditForm>

@code {
    private PinModel _model = new PinModel();

    void OnValidSubmit()
    {

    }

    void OnInvalidSubmit()
    {
        _model.Pin = null;
        _model.PinConfirmation = null;
        StateHasChanged();
    }
}

PinModel.cs

public class PinModel
{
    public string Pin { get; set; }

    public string PinConfirmation { get; set; }
}

以下 this example repo 我使用了 FluentValidation

EditContextFluentValidationExtensions.cs

public static class EditContextFluentValidationExtensions
    {
        public static EditContext AddFluentValidation(this EditContext editContext)
        {
            if (editContext == null)
            {
                throw new ArgumentNullException(nameof(editContext));
            }

            var messages = new ValidationMessageStore(editContext);

            editContext.OnValidationRequested +=
                (sender, eventArgs) => ValidateModel((EditContext)sender, messages);

            editContext.OnFieldChanged +=
                (sender, eventArgs) => ValidateField(editContext, messages, eventArgs.FieldIdentifier);

            return editContext;
        }

        private static void ValidateModel(EditContext editContext, ValidationMessageStore messages)
        {
            var validator = GetValidatorForModel(editContext.Model);

            if (validator == null)
                return;

            var validationResults = validator.Validate(editContext.Model);

            messages.Clear();
            foreach (var validationResult in validationResults.Errors)
            {
                messages.Add(editContext.Field(validationResult.PropertyName), validationResult.ErrorMessage);
            }

            editContext.NotifyValidationStateChanged();
        }

        private static void ValidateField(EditContext editContext, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
        {
            var properties = new[] { fieldIdentifier.FieldName };
            var context = new ValidationContext(fieldIdentifier.Model, new PropertyChain(), new MemberNameValidatorSelector(properties));

            var validator = GetValidatorForModel(fieldIdentifier.Model);

            if (validator == null)
                return;

            var validationResults = validator.Validate(context);

            messages.Clear(fieldIdentifier);

            foreach (var validationResult in validationResults.Errors)
            {
                messages.Add(editContext.Field(validationResult.PropertyName), validationResult.ErrorMessage);
            }

            editContext.NotifyValidationStateChanged();
        }

        private static IValidator GetValidatorForModel(object model)
        {
            var abstractValidatorType = typeof(AbstractValidator<>).MakeGenericType(model.GetType());
            var modelValidatorType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t => t.IsSubclassOf(abstractValidatorType));

            if (modelValidatorType == null)
                return null;

            var modelValidatorInstance = (IValidator)Activator.CreateInstance(modelValidatorType);

            return modelValidatorInstance;
        }
    }

FluentValidationValidator.cs

public class FluentValidationValidator : ComponentBase
    {
        [CascadingParameter] 
        EditContext CurrentEditContext { get; set; }

        protected override void OnInitialized()
        {
            if (CurrentEditContext == null)
            {
                throw new InvalidOperationException($"{nameof(FluentValidationValidator)} requires a cascading " +
                    $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(FluentValidationValidator)} " +
                    $"inside an {nameof(EditForm)}.");
            }

            CurrentEditContext.AddFluentValidation();
        }
    }

PinValidator.cs

public class PinValidator : AbstractValidator<PinModel>
    {
        public PinValidator()
        {
            RuleFor(p => p.Pin).NotNull().Matches("^[0-9]{4}$");
            RuleFor(p => p).Must(PinsAreSame)
                .WithMessage("PINs must be the same");
        }

        private bool PinsAreSame(PinModel pinModel)
        {
            return (pinModel.Pin.Equals(pinModel.PinConfirmation));
        }
    }