Marvin Klein
Marvin Klein

Reputation: 1746

Custom Blazor component for InputText

I want to simplify my code by wrapping a large chunk of form code into a seperate component.

What I did was creating MyInput.razor which holds the following code:

<div class="@ColSize">
    <label>@Label</label>
    <InputText @bind-Value="Value" class="form-control" />
    <ValidationMessage For="() => Value" />
</div>

@code {
    public string ColSize { get; set; } = "col-md-12";
    [Parameter]
    public string Label { get; set; } = string.Empty;
    [Parameter]
    public string? Value { get; set; }
    [Parameter]
    public EventCallback<string?> ValueChanged { get; set; }
}

In my Index.razor I've added a simple form with basic validation like this:

<EditForm Model="Input" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <MyInput @bind-Value="Input.Name" />
    
    <button type="submit" class="d-block mt-3">Save</button>
</EditForm>

@code {
    public MyModel Input { get; set; } = new();
    private void Submit()
    {

    }
    public class MyModel
    {
        [MinLength(10)]
        public string Name { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
    }
}

The validation and the binding is working, however, I don't see the validation errors for <ValidationMessage For="() => Value" /> in my component. I can only see the messages with the ValidationSummary.

Is there any easy way to get this working? I don't want to create a new component which inherits from InputText or InputBase. I just want to use the existing input types and wrap some HTML around it so I don't have to type it everytime again.

Upvotes: 2

Views: 4019

Answers (2)

Dmitry Sikorsky
Dmitry Sikorsky

Reputation: 1501

It could be better to inherit your component from InputBase<string> in this case, then you do not need to provide expression for validation.

Also, you can read label from Display data annotation attribute.

@using System.ComponentModel.DataAnnotations
@using System.Reflection
@inherits InputBase<string>

<div class="@CssClass field">
  @if (!string.IsNullOrWhiteSpace(this.Label))
  {
    <label class="field__label">@Label</label>
  }
  <input class="form-field__input" @bind="@CurrentValue" />
  @if (this.ValueExpression != null)
  {
    <ValidationMessage class="field__validation-message" For=@ValueExpression />
  }
</div>

@code {
  private string? Label
  {
    get
    {
      if (ValueExpression != null)
        if (ValueExpression.Body is MemberExpression memberExpression)
          return (memberExpression.Member as PropertyInfo)?.GetCustomAttribute<DisplayAttribute>()?.GetName();

      return null;
    }
  }

  protected override bool TryParseValueFromString(string? value, out string result, out string validationErrorMessage)
  {
    result = value ?? string.Empty;
    validationErrorMessage = string.Empty;
    return true;
  }
}

Usage:

<EditForm Model="ViewModel" OnValidSubmit="Submit">
  <DataAnnotationsValidator />
  <ValidationSummary />
  <TextualField @bind-Value="ViewModel.Email" />
  <SecretField @bind-Value="ViewModel.Password" />
  <button type="submit">Save</button>
</EditForm>

Upvotes: 0

Brian Parker
Brian Parker

Reputation: 14553

<ValidationMessage uses and expression to get to the model EditForm is using. You need to pass the expression into your component.

<div class="@ColSize">
    <label>@Label</label>
    <InputText @bind-Value="Value" class="form-control" />
    <ValidationMessage For=@For />
</div>

@code {
    public string ColSize { get; set; } = "col-md-12";
    [Parameter] public string Label { get; set; } = string.Empty;
    [Parameter] public string? Value { get; set; }
    [Parameter] public Expression<Func<TValue>>? For { get; set; } 
    [Parameter] public EventCallback<string?> ValueChanged { get; set; }
}

Usage:

<MyInput @bind-Value="Input.Name" For="() => Input.Name" />

Source code for ValidationMessage

Upvotes: 5

Related Questions