简体   繁体   中英

blazor editcontext validate for single field

I have a blazor component and using editcontext with data annotations. I'm trying to validate on field at a time on keypress or onblur instead of validating entire form. can someone please help me with this issue:

Below is my code

<EditForm EditContext="@editContext">
  <DataAnnotationsValidator />    
  <div class="custom-group">
    <label class="custom-label">Year Of Birth: </label>
    <InputText @bind-Value="model.YearOfBirth" @onkeypress="KeyboardEventHandler" @onfocus="onFocusEvent" @onblur="onBlurEvent" class="customInputTextBox"/>
    <ValidationMessage For="() => model.YearOfBirth" />
  </div>
  <div class="custom-group">
    <label class="custom-label"> Drivers License Id:</label>
    <InputText @bind-Value="model.DriversLicenseId" @onkeypress="KeyboardEventHandler" @onfocus="onFocusEvent" @onblur="onBlurEvent" class="customInputTextBox"/>
    <ValidationMessage For="() => model.DriversLicenseId" />
  </div>
</EditForm>

private EditContext editContext {get; set;}
private Model model = new () {};
protected override void OnInitialized()
{
  editContext = new(model);
}

On the normal Blazor Input controls update occurs when you exit the control.

To wire them up for the oninput event, you need to extend the existing controls. I've added the UpdateOnInput parameter to control which event the update is wired to.

Here's MyInputText :

public class MyInputText : InputText
{
    [Parameter] public bool UpdateOnInput { get; set; } = false;

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "input");
        builder.AddMultipleAttributes(1, AdditionalAttributes);
        if (!string.IsNullOrWhiteSpace(this.CssClass))
            builder.AddAttribute(2, "class", CssClass);

        builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValue));

        if (this.UpdateOnInput)
            builder.AddAttribute(4, "oninput", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
        else
            builder.AddAttribute(5, "onchange", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));

        builder.AddElementReferenceCapture(6, __inputReference => Element = __inputReference);
        builder.CloseElement();
    }
}

The original is here: https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputText.cs

And MyInputNumber :

public class MyInputNumber<TValue> : InputNumber<TValue>
{
    [Parameter] public bool UpdateOnInput { get; set; } = false;
    
    private static readonly string _stepAttributeValue = GetStepAttributeValue();

    private static string GetStepAttributeValue()
    {
        // Unwrap Nullable<T>, because InputBase already deals with the Nullable aspect
        // of it for us. We will only get asked to parse the T for nonempty inputs.
        var targetType = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
        if (targetType == typeof(int) ||
            targetType == typeof(long) ||
            targetType == typeof(short) ||
            targetType == typeof(float) ||
            targetType == typeof(double) ||
            targetType == typeof(decimal))
        {
            return "any";
        }
        else
        {
            throw new InvalidOperationException($"The type '{targetType}' is not a supported numeric type.");
        }
    }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "input");
        builder.AddAttribute(1, "step", _stepAttributeValue);
        builder.AddMultipleAttributes(2, AdditionalAttributes);
        builder.AddAttribute(3, "type", "number");

        if (!string.IsNullOrWhiteSpace(this.CssClass))
            builder.AddAttribute(4, "class", CssClass);

        builder.AddAttribute(5, "value", BindConverter.FormatValue(CurrentValueAsString));

        if (this.UpdateOnInput)
            builder.AddAttribute(6, "oninput", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
        else
            builder.AddAttribute(7, "onchange", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));

        builder.AddElementReferenceCapture(8, __inputReference => Element = __inputReference);
        builder.CloseElement();
    }
}

Original here - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputNumber.cs

Here's my test form:

@page "/"
@using System.ComponentModel.DataAnnotations;

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

<EditForm EditContext=this.editContext>
    <DataAnnotationsValidator />
    <div class="row my-2">
        <div class="col">
            <div class="form-label">Value:</div>
            <MyInputNumber UpdateOnInput=true class="form-control" @bind-Value=this.model.Value />
            <ValidationMessage For="() => this.model.Value" />
        </div>
    </div>
    <div class="row my-2">
        <div class="col">
            <div class="form-label">First Name:</div>
            <InputText class="form-control" @bind-Value=this.model.FirstName />
            <ValidationMessage For="() => this.model.FirstName" />
        </div>
    </div>
    <div class="row my-2">
        <div class="col">
            <div class="form-label">Last Name:</div>
            <MyInputText UpdateOnInput=true class="form-control" @bind-Value=this.model.LastName />
            <ValidationMessage For="() => this.model.LastName" />
        </div>
    </div>
    <div class="row my-2">
        <div class="col">
            <div class="alert alert-primary mt-3">
                First Name: @this.model.FirstName - Last Name: @this.model.LastName - Value : @this.model.Value
            </div>
        </div>
    </div>
    <div class="row my-2">
        <div class="col text-end">
            <button type="submit" class="btn btn-success">Submit</button>
        </div>
    </div>
</EditForm>

@code {
    private MyModel model = new MyModel { FirstName = "Shaun", LastName="Curtis" };
    private EditContext? editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(model);
        base.OnInitialized();
    }
    public class MyModel
    {
        [StringLength(10, ErrorMessage = "Too long (10 character limit).")]
        public string FirstName { get; set; } = string.Empty;

        [StringLength(10, ErrorMessage = "Too long (10 character limit).")]
        public string LastName { get; set; } = string.Empty;

        [Range(1, 10, ErrorMessage = "Wrong! (1-10).")]
        public int Value { get; set; }
    }
}

Based on these you should be able to find the code for the other controls and extend them yourself. Let me know if you run into difficulties and I'll look at the specific control.

Here's an example with the cursor in the Value control (just added 0):

在此处输入图像描述

The following code snippet describes how you can subscribe to the OnFieldChanged event of the EditContext object and perform validation specific to a given field (EmailAddress). The OnFieldChanged event is raised when a field value changes (this happens when you navigate out of the input text box, right?). The code simulate a call to a database to verify that the provided email does not exist in the database. If the email address do exist in the database, a validation message is displayed to let you know about this. After you type a new email address, your input passes validation, and the message is removed... If you enter the text enet.studio1@gmail.com , input validation will fail, as this is en email address that supposedly exists in the database. Copy and test:

@page "/"
@using System.ComponentModel.DataAnnotations

<EditForm EditContext="@EditContext" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />

    <div class="form-group">
        <label for="name">Name: </label>
        <InputText Id="name" Class="form-control" @bind-Value="@Model.Name"></InputText>
        <ValidationMessage For="@(() => Model.Name)" />

    </div>
    <div class="form-group">
        <label for="body">Text: </label>
        <InputText Id="body" Class="form-control" @bind-Value="@Model.Text"></InputText>
        <ValidationMessage For="@(() => Model.Text)" />
    </div>
    <div class="form-group">
        <label for="body">Email: </label>
        <InputText Id="body" Class="form-control" @bind-Value="@Model.EmailAddress" ></InputText>
        <ValidationMessage For="@(() => Model.EmailAddress)" />
    </div>
    <p>

        <button type="submit">Save</button>

    </p>
</EditForm>

@code
    {

    private EditContext EditContext;
    private Comment Model = new Comment();
    ValidationMessageStore messages;

    protected override void OnInitialized()
    {
        EditContext = new EditContext(Model);
        EditContext.OnFieldChanged += EditContext_OnFieldChanged;
        messages = new ValidationMessageStore(EditContext);

    }


// Note: The OnFieldChanged event is raised for each field in the model
   private async void EditContext_OnFieldChanged(object sender, 
                                        FieldChangedEventArgs e)
    {
    
        await Task.Yield();

        var propertyValue = Model.EmailAddress;

        var emailExist = await EmailExist(propertyValue);

        if (emailExist)
        {
            messages.Clear(e.FieldIdentifier);
            messages.Add(e.FieldIdentifier, "Email already exists...");
        }
        else
        {
           messages.Clear(e.FieldIdentifier);
        }
     
       EditContext.NotifyValidationStateChanged();

    }


    // Simulate a database call to verify that the provided email does not exist in the database
    private async Task<bool> EmailExist(string emailAddress)
    {
        await Task.Yield();

        if(emailAddress == "enet.studio1@gmail.com")
            return true;

        return false;
    }

    public async Task HandleValidSubmit()
    {
        await Task.Run(() =>
        {
            Console.WriteLine("Saving...");
            Console.WriteLine(Model.Name);
            Console.WriteLine(Model.Text);
            Console.WriteLine(Model.EmailAddress);
        });
    }

    public class Comment
    {
        [Required]
        [MaxLength(10)]
        public string Name { get; set; }

        [Required]
        public string Text { get; set; }

        [Required]
        [EmailAddress]
        [DataType(DataType.EmailAddress)]
        public string EmailAddress { get; set; }
    }

}

It is not clear what validation you want to perform within the keypress event, so I will only focus on how you perform the validation, ignoring the intricacies of this event. Here's a rudimentary of validation performed in the keypress event:

void KeyHandler(KeyboardEventArgs args)
{
    // Read a key pressed at a time
    var propertyValue = args.Key;

    var fieldIdentifier = new FieldIdentifier(Model, "EmailAddress");

    // if the key "x" was pressed
    if (propertyValue == "x")
    {
        messages.Clear();
        messages.Add(fieldIdentifier, "Do not use `x` in your email 
                                                       address.");
    }
    else
    {
        messages.Clear();

    }

    EditContext.NotifyValidationStateChanged();
}

Here's a complete code snippet. Copy and text:

@page "/"
@using System.ComponentModel.DataAnnotations

<EditForm EditContext="@EditContext" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />

    <div class="form-group">
        <label for="name">Name: </label>
        <InputText Id="name" Class="form-control" @bind-Value="@Model.Name"></InputText>
        <ValidationMessage For="@(() => Model.Name)" />

    </div>
    <div class="form-group">
        <label for="body">Text: </label>
        <InputText Id="body" Class="form-control" @bind-Value="@Model.Text"></InputText>
        <ValidationMessage For="@(() => Model.Text)" />
    </div>
    <div class="form-group">
        <label for="body">Email: </label>
        <InputText Id="body" Class="form-control" @bind-Value="@Model.EmailAddress" @onkeypress="KeyHandler"></InputText>
        <ValidationMessage For="@(() => Model.EmailAddress)" />
    </div>
    <p>

        <button type="submit">Save</button>

    </p>
</EditForm>

@code
    {

    private EditContext EditContext;
    private Comment Model = new Comment();
    ValidationMessageStore messages;

    protected override void OnInitialized()
    {
        EditContext = new EditContext(Model);
        messages = new ValidationMessageStore(EditContext);

    }

   void KeyHandler(KeyboardEventArgs args)
   {
    // Read a key pressed at a time
    var propertyValue = args.Key;

    var fieldIdentifier = new FieldIdentifier(Model, "EmailAddress");

    // if the key "x" was pressed
    if (propertyValue == "x")
    {
        messages.Clear();
        messages.Add(fieldIdentifier, "Do not use `x` in your email 
                                                       address.");
    }
    else
    {
        messages.Clear();

    }

    EditContext.NotifyValidationStateChanged();
  }
   

    public async Task HandleValidSubmit()
    {
        await Task.Run(() =>
        {
            Console.WriteLine("Saving...");
            Console.WriteLine(Model.Name);
            Console.WriteLine(Model.Text);
            Console.WriteLine(Model.EmailAddress);
        });
    }

    public class Comment
    {
        [Required]
        [MaxLength(10)]
        public string Name { get; set; }

        [Required]
        public string Text { get; set; }

        [Required]
        [EmailAddress]
        [DataType(DataType.EmailAddress)]
        public string EmailAddress { get; set; }
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM