Статьи

Помощник по вводу в форму

Написание линейки бизнес-приложений обычно означает создание множества форм для ввода данных. Написание HTML для них снова и снова утомительно и также означает копирование и вставку структуры макета в каждую отдельную форму. Копирование отлично работает, пока мы довольны дизайном, но когда его нужно изменить (помимо того, что возможно с помощью CSS), все формы в приложении должны измениться. Чтобы исправить это, я создал помощник тега ввода формы. Теперь создать запись для поля в форме так же просто, как <form-entry asp-for="LocationName" />.

Используя стандартные леса в Visual Studio, я бы получил форму, которая повторяет один и тот же шаблон снова и снова для каждого свойства модели представления.

<form asp-action="Create">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Name" class="control-label"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Address" class="control-label"></label>
        <input asp-for="Address" class="form-control" />
        <span asp-validation-for="Address" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="City" class="control-label"></label>
        <input asp-for="City" class="form-control" />
        <span asp-validation-for="City" class="text-danger"></span>
    </div>
    <div class="form-group">
      <input type="submit" value="Create" class="btn btn-default" />
    </div>
</form>

Используя мой form-entryпомощник тегов, требуемый код значительно меньше.

<form asp-action="Create">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <form-entry asp-for="Name" />
    <form-entry asp-for="Address" />
    <form-entry asp-for="City" />
    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-default"/>
    </div>
</form>

Цели дизайна

Цели проекта для form-entryпомощника по тегам можно суммировать как СУХОЕ . СУХОЙ — это сокращение от « Не повторяйся сам» . Одной из причин этого является сокращение набора текста. Но это не самая важная часть. Нет, важными элементами являются удобочитаемость и ремонтопригодность. С помощью form-entryпомощника по тегам код становится намного чище, и легче увидеть, из каких полей форма фактически состоит.

Другой важной частью является ремонтопригодность. Повторять один и тот же HTML-шаблон снова и снова во всех формах приложения означает распространение знаний о том, как формы выглядят в приложении. Это знание должно быть в одном месте.

Использование бритвы

Я также подумал, что было бы неплохо иметь возможность использовать Razor для самого шаблона, чтобы можно было легко вызывать помощники тегов для создания меток и т. Д. Правильно. К сожалению, мне не удалось достичь этой цели. Причина в том, что помощники тегов построены на предположении, что файл бритвы содержит тип модели. И здесь мы хотим иметь возможность использовать общий файл Razor для всех различных моделей в проекте.

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

Когда путь Razor оказался невозможным, я вместо этого обратился к вызову помощников по тегам из кода. Это тоже оказалось трудным делом, так как потребовало бы создания правильного контекста для помощника тега. Но благодаря многоуровневой архитектуре помощников тегов, повторное использование было все еще возможно. Встроенные помощники тегов сами по себе не генерируют HTML. Вместо этого они полагаются на это IHtmlGenerator. И IHtmlGeneratorоказалось, что звонить из моего пользовательского помощника по тегам оказалось довольно просто.

Код

public class FormEntryTagHelper: TagHelper
{
  private readonly IHtmlGenerator htmlGenerator;
  private readonly HtmlEncoder htmlEncoder;

  public FormEntryTagHelper(IHtmlGenerator htmlGenerator, HtmlEncoder htmlEncoder)
  {
    this.htmlGenerator = htmlGenerator;
    this.htmlEncoder = htmlEncoder;
  }

  private const string ForAttributeName = "asp-for";

  [HtmlAttributeName(ForAttributeName)]
  public ModelExpression For { get; set; }

  [HtmlAttributeNotBound]
  [ViewContext]
  public ViewContext ViewContext { get; set; }

  public override void Process(TagHelperContext context, TagHelperOutput output)
  {
    output.TagName = "div";
    output.TagMode = TagMode.StartTagAndEndTag;
    output.Attributes.Add("class", "form-group");

    using (var writer = new StringWriter())
    {
      WriteLabel(writer);
      WriteInput(writer);
      WriteValidation(writer);
      output.Content.AppendHtml(writer.ToString());
    }
  }

  private void WriteLabel(TextWriter writer)
  {
    var tagBuilder = htmlGenerator.GenerateLabel(
      ViewContext,
      For.ModelExplorer,
      For.Name,
      labelText: null,
      htmlAttributes: new { @class = "control-label" });

    tagBuilder.WriteTo(writer, htmlEncoder);
  }

  private void WriteInput(TextWriter writer)
  {
    var tagBuilder = htmlGenerator.GenerateTextBox(
      ViewContext,
      For.ModelExplorer,
      For.Name,
      value: null,
      format: null,
      htmlAttributes: new { @class = "form-control" });

    tagBuilder.WriteTo(writer, htmlEncoder);
  }

  private void WriteValidation(TextWriter writer)
  {
    var tagBuilder = htmlGenerator.GenerateValidationMessage(
      ViewContext,
      For.ModelExplorer,
      For.Name,
      message: null,
      tag: null,
      htmlAttributes: new { @class = "text-danger" });

    tagBuilder.WriteTo(writer, htmlEncoder);
  }
}