Статьи

Подтверждение формы Blazor

Клиентский Blazor поддерживает проверку формы DataAnnotations прямо из коробки. Это просто и интуитивно понятно, но также очень гибко. При необходимости мы можем использовать тот же механизм для замены проверки DataAnnotations каким-либо другим компонентом проверки. В этом блоге рассказывается о проверке формы в приложениях Blazor и рассматривается механизм проверки.

Проверка формы в Blazor является экспериментальной и может быть изменена. Этот пост написан с использованием .NET Core 3.0 Preview 7.

Проверка аннотаций данных

Blazor поддерживает валидацию DataAnnotations прямо из коробки. Чтобы использовать проверку, мы должны иметь модель с аннотациями данных и форму редактирования, определенную в представлении Blazor.

Давайте создадим простую модель для гостевой книги. У этого есть свойства для названия плаката и сообщения.

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

Вот форма Blazor, которая использует готовую форму проверки. Я следовал тому же шаблону с сообщениями проверки, которые должны быть знакомы, по крайней мере, с теми, кто знает ASP.NET MVC.

@page "/"
@using  BlazorFormValidation.Models 

<h1>My guestbook</h1> 

<p>Leave me a message if you like my site</p> 

<EditForm Model="@Model" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit">    
  <div class="alert @StatusClass">@StatusMessage</div>        
  
  <DataAnnotationsValidator />    
  <ValidationSummary />        
  
  <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>        
    <InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text"></InputTextArea>        
    <ValidationMessage For="@(() => Model.Text)" />    
  </div>    
  <button type="submit">Ok</button> 

</EditForm> 

@code {    
	private string StatusMessage;    
	private string StatusClass;     

	private GuestbookEntry Model = new GuestbookEntry();     
	
	protected void HandleValidSubmit()    
	{        
		StatusClass = "alert-info";        
		StatusMessage = DateTime.Now + " Handle valid submit";    
	}     

	protected void HandleInvalidSubmit()    
	{        
		StatusClass = "alert-danger";        
		StatusMessage = DateTime.Now + " Handle invalid submit";    
	}
}

EditForm — это контейнер Blazor для форм. Validator добавляется как любой другой компонент Blazor. На странице выше используется DataAnnotationValidator, но вы можете создать свой, если хотите.

Вот скриншот, демонстрирующий форму, когда поля пусты и нажата кнопка Ok.

Форма гостевой книги Blazor недействительна

Это форма с успешной проверкой.

Форма гостевой книги Blazor действительна

Я думаю, что большинство разработчиков будут в порядке с этим решением, и я могу закончить это написание. Или нет?

Кнопка Отключить, пока форма не будет действительной

Мы действительно можем копать глубже и попробовать что-то необычное. Что если мы хотим, чтобы кнопка Ok была отключена, когда форма недействительна? Я нашел в блоге отключив кнопку Отправить в Blazor Validation от Питера Himschoot , где он обеспечивает решение через пользовательский InputWatcher.

Мое решение меньше. Мы можем назначить EditForm либо Model, либо EditContext, но не оба одновременно. При использовании EditContext у нас гораздо больше контроля над проверкой. Таким образом, я решил необходимость дополнительных компонентов.

Я решил проблему состояния кнопки с помощью простого, но не очень простого трюка. Вместо логического значения для отключенного атрибута я использовал строку. Значение либо «отключено», либо равно нулю. Обратите внимание, что пустая строка оставляет атрибут «disabled» на кнопке. Я должен был слушать событие смены поля так же, как Питер.

@page "/"
@using  BlazorFormValidation.Models 

<h1>My guestbook</h1> 

<p>Leave me a message if you like my site</p> 

<EditForm EditContext="@EditContext">       
	<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>        
        <InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text"></InputTextArea>        
        <ValidationMessage For="@(() => Model.Text)" />    
    </div>    
    <button type="submit" disabled="@OkayDisabled">Ok</button> 

</EditForm> 
    
@code 
{    
	private EditContext EditContext;    
    private GuestbookEntry Model = new GuestbookEntry();     
          
    protected string OkayDisabled { get; set; } = "disabled";     
          
    protected override void OnInit()    
    {        
      	EditContext = new EditContext(Model);        
      	EditContext.OnFieldChanged += EditContext_OnFieldChanged;         
      
      	base.OnInit();    
    }     
          
    protected override void OnAfterRender()    
    {        
    	base.OnAfterRender();         
      
      	SetOkDisabledStatus();    
    }     
          
    private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)    
    {        
      	SetOkDisabledStatus();    
    }     
          
    private void SetOkDisabledStatus()    
    {        
      	if(EditContext.Validate())        
        {            
          	OkayDisabled = null;        
        }        
      	else        
        {            
          	OkayDisabled = "disabled";        
        }    
    }
 }

Вот как открывается гостевая книга, когда я ее запускаю. Валидационные сообщения показываются для полей, а кнопка Ok отключена.

Гостевая книга Blazor: кнопка недоступна, если форма недействительна

С классом EditContext мы смогли принять форму под нашим контролем.

Внутри класса EditContext

Там больше о EditContext. Посмотрите на определение класса EditContext.

public sealed class EditContext
{    
  public EditContext(object model);    
  public object Model { get; }     
  public event EventHandler<FieldChangedEventArgs> OnFieldChanged;    
  public event EventHandler<ValidationRequestedEventArgs> OnValidationRequested;    
  public event EventHandler<ValidationStateChangedEventArgs> OnValidationStateChanged;     
  
  public FieldIdentifier Field(string fieldName);    
  public IEnumerable<string> GetValidationMessages();    
  public IEnumerable<string> GetValidationMessages(FieldIdentifier fieldIdentifier);    
  public bool IsModified();    
  public bool IsModified(in FieldIdentifier fieldIdentifier);    
  public void MarkAsUnmodified(in FieldIdentifier fieldIdentifier);    
  public void MarkAsUnmodified();    
  public void NotifyFieldChanged(in FieldIdentifier fieldIdentifier);    
  public void NotifyValidationStateChanged();    
  public bool Validate();
}

У него есть события, которые запускаются для изменений. У нас есть методы, чтобы пометить поля как неизмененные; легко получить идентификатор поля, используемый Blazor, и уведомить EditContext об изменениях, если нам это нужно.

Проверьте статус проверки при изменении поля

Есть еще одна мелочь, которую нужно решить в нашей форме. Когда форма станет действительной, я хочу, чтобы кнопка Ok была включена в тот же момент, а не когда я покидаю поле. Эта функциональность будет полезна для полей, проверяемых регулярным выражением, например.

После некоторого исследования и взлома я нашел уродливое решение, использующее событие и рефлексию KeyUp (я уверен, что парни из Blazor не хотят, чтобы вы делали это дома). Но вот мое решение.

@page "/"
  @using BlazorFormValidation.Models 
  
  <h1>My guestbook</h1> 
  
  <p>Leave me a message if you like my site</p> 
  
    <EditForm EditContext="@EditContext">    
    	<DataAnnotationsValidator />     
    
    <div class="form-group">        
    	<label for="name">Name: </label>        
        <InputText Id="name" Class="form-control" @bind-Value="@Model.Name" onkeyup='@(e => KeyUp(e, "Name"))'>
   </InputText>        
   		<ValidationMessage For="@(() => Model.Name)" />    
    </div>    
    
    <div class="form-group">        
    	<label for="body">Text: </label>        
        <InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text" onkeyup='@(e => KeyUp(e, "Text"))'>
    </InputTextArea>        
    	<ValidationMessage For="@(() => Model.Text)" />    
    </div>    
    <button type="submit" disabled="@OkayDisabled">Ok</button> 
    
    </EditForm> 
    
    @code
    {    
    	private EditContext EditContext;    
        private GuestbookEntry Model = new GuestbookEntry();     
          
        protected string OkayDisabled { get; set; } = "disabled";     
          
        protected override void OnInit()    
        {        
          EditContext = new EditContext(Model);        
          EditContext.OnFieldChanged += EditContext_OnFieldChanged;         
          
          base.OnInit();    
        }     
          
        protected override void OnAfterRender()    
        {        
          base.OnAfterRender();         
          
          SetOkDisabledStatus();    
        }     
          
        private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)    
        {        
          SetOkDisabledStatus();    
        }     
          
        void KeyUp(UIKeyboardEventArgs e, string memberName)    
        {        
          var property = Model.GetType().GetProperty(memberName);        
          var value = property.GetValue(Model);        
          property.SetValue(Model, value + e.Key);         
          
          var id = EditContext.Field(memberName);         
          
          EditContext.NotifyFieldChanged(id);    
        }     
          
        private void SetOkDisabledStatus()    
        {        
          if (EditContext.Validate())        
          {            
            OkayDisabled = null;        
          }        
          else        
          {            
            OkayDisabled = "disabled";        
          }    
        }
     }

Это мелочь, но пользователям это понравится. Мы просто сделали нашу форму еще более отзывчивой.

Использование других валидаторов

Я нашел  поддержку FluentValidation для Blazor от Chris SaintyFluentValidation — это популярная библиотека проверки, которая поддерживает также расширенные сценарии проверки.

С FluentValidation мы используем классы валидаторов, подобные показанному здесь.

public class GuestbookEntryValidator : AbstractValidator<GuestbookEntry>
{    
	public GuestbookEntryValidator()    
    {        
    	RuleFor(e => e.Name)                
        	.NotEmpty().WithMessage("Name is required")                
            .MaximumLength(10).WithMessage("10 characters maximum");         
        
        RuleFor(e => e.Text)                
        	.NotEmpty().WithMessage("Text is required");    
    }
}

Наша форма не сильно меняется. Нам просто нужно изменить компонент валидатора.

<EditForm EditContext="@EditContext">    
  <FluentValidationValidator />     
  
  <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>        
    <InputTextArea Id="body" Class="form-control" @bind-Value="@Model.Text"></InputTextArea>        
    <ValidationMessage For="@(() => Model.Text)" />    
  </div>    
  <button type="submit" disabled="@OkayDisabled">Ok</button> 

</EditForm>

Теперь мы можем использовать всю мощь FluentValidator в наших формах. Проверьте исходный код FluentValidationValidator из репозитория Github chrissainty / FluentValidationWithRazorComponents .

Завершение

Существует два способа проверки на Blazor. Мы можем использовать свойство Model EditForm и упростить для нас задачу. Если нам нужно больше контроля над проверкой и пользовательским интерфейсом, мы можем предоставить EditContext для EditForm. Те, кто хочет углубиться во внутреннюю функциональность, могут увидеть, как Christ Sainty интегрировал FluentValidation в Blazor. Хотя в следующих версиях Blazor могут быть изменения для проверки формы, мы все же можем приступить к созданию наших собственных расширенных компонентов для использования с приложениями Blazor.