How do I use <ValidationMessage> within a component

[亡魂溺海] 提交于 2021-02-10 14:17:18

问题


Please note that although this is closely related to this question, the answer there is specific to one known model property, whereas I want this to work for any property, so the component is reusable.

I want to use the Blazor <ValidationMessage> tag within a component. However, when I do this, the validation message isn't shown.

For example, the following component (FormRowText.razor) creates a row (in the Bootstrap grid sense) containing an <input type="text /> for a named property on a model...

<div class="form-group row">
  <label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
  <div class="col-lg-10 input-group">
    <div class="input-group-prepend">
      <span class="input-group-text">
        <i class="far fa-@Icon"></i>
      </span>
    </div>
    <input class="form-control" Value="@Value" type="text"
           id="@PropertyName" name="@PropertyName" @onchange="@OnChanged" />
  </div>
</div>

@code {

  [Parameter]
  public string PropertyName { get; set; }

  [Parameter]
  public string Value { get; set; }

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

  [Parameter]
  public string Caption { get; set; }

  [Parameter]
  public string Icon { get; set; }

  private async Task OnChanged(ChangeEventArgs cea) =>
    await ValueChanged.InvokeAsync(cea.Value?.ToString() ?? "");

}

It is used like this...

<EditForm ...>
  <FormRowText PropertyName="@nameof(Customer.Email)" @bind-Value="_customer.Email" Icon="at" />
</EditForm>

If I add a <ValidationMessage> after this markup, it works fine, but if I add it inside the component (which is where I want it to be), it doesn't work.

I saw this answer from @enet, which shows how to do this for a specific, known model property. However, my component is designed to work with any model property, and I'm not sure how to modify it.

I tried adding a parameter for the model (with @typeparam T at the top of the file)...

[Parameter]
public T Model { get; set; }

...and adding the following inside the HTML...

<ValidationMessage For="() => PropertyName" />

However this doesn't work.

Any ideas?


回答1:


You can cascade the EditContext to your component and use it to notify that the current field has changed

FormRowText.razor.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;    

public partial class FormRowText
    {
        [CascadingParameter] EditContext CascadedEditContext { get; set; }

        [Parameter]
        public string PropertyName { get; set; }

        [Parameter]
        public string Value { get; set; }

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

        [Parameter]
        public string Caption { get; set; }

        [Parameter]
        public string Icon { get; set; }

        [Parameter] public Expression<Func<string>> ValueExpression { get; set; }
        private async Task OnChanged(ChangeEventArgs cea)
        {
            await ValueChanged.InvokeAsync(cea.Value?.ToString() ?? "");
            var identifier = CascadedEditContext.Field(PropertyName);
            CascadedEditContext.NotifyFieldChanged(identifier);
        }
         
    }

FormRowText.razor

<div class="form-group row">
    <label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
    <div class="col-lg-10 input-group">
        <div class="input-group-prepend">
            <span class="input-group-text">
                <i class="far fa-@Icon"></i>
            </span>
        </div>
        <input class="form-control" value="@Value" type="text" id="@PropertyName" name="@PropertyName" 
               @onchange="@OnChanged" />
        <ValidationMessage For="@ValueExpression" />

    </div>
</div>

You'll also need

public class Customer
    {
        [Required]
        public string Name { get; set; }
        public Country Country { get; set; } = Country.Israel;
        [Required]
        [DataType(DataType.EmailAddress)]
        [EmailAddress]
        public string Email { get; set; }
    } 

Usage

@page "/"


    @using System.ComponentModel.DataAnnotations;
    @using Microsoft.AspNetCore.Components.Forms;


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

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

        </div>
        <div class="form-group">
            <FormRowDropdownEnum @bind-Value="customer.Country" Caption="Country" T="Country"></FormRowDropdownEnum>
            <ValidationMessage For="@(() => customer.Country)" />
            <FormRowText @bind-Value="customer.Email" Caption="Email" 
                 PropertyName="@nameof(Customer.Email)" Icon="at"></FormRowText>


        </div>
        <p>
            <button type="submit" class="btn btn-primary">Save</button>

        </p>
    </EditForm>


  
    @code{
        private Customer customer = new Customer();
        private EditContext EditContext;

        protected override void OnInitialized()
        {
            EditContext = new EditContext(customer);

        }

        public async Task HandleValidSubmit()
        {
            await Task.Delay(1);

            Console.WriteLine("Saving...");
           
        }
   }

Another version

As you're using the EditForm component, it'd be a good idea to use the Forms components, such as the InputText component and such like.

The following code snippet describes how to implement the FormRowText component by sub-classing the InputText components, and adding new properties. Note that the PropertyName is not required for the functioning of the component

FormRowText2.razor

@inherits InputText

<div class="form-group row">
    <label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
    <div class="col-lg-10 input-group">
        <div class="input-group-prepend">
            <span class="input-group-text">
                <i class="far fa-@Icon"></i>
            </span>
        </div>
        <input class="form-control" @bind="CurrentValue" type="text" id="@PropertyName" name="@PropertyName" />
        <ValidationMessage For="@ValueExpression" />
     </div>
</div>

@code{
    [Parameter]
    public string Caption { get; set; }

    [Parameter]
    public string Icon { get; set; }

    [Parameter]
    public string PropertyName { get; set; }
    
}


来源:https://stackoverflow.com/questions/65224446/how-do-i-use-validationmessage-within-a-component

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!