Настройка шаблонизированных вспомогательных методов

154

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

Создание специального шаблона редактора

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

Чтобы продемонстрировать работу этого средства, мы создадим специальный шаблон для свойства Role класса User. Типом этого свойства является перечисление Role, но способ стандартной его визуализации сопряжен с проблемой, т.к. шаблонизированные вспомогательные методы просто создают обычный элемент <input>, который позволяет пользователю вводить любое значение, а не только значения, определенные этим перечислением.

Инфраструктура ASP.NET MVC Framework ищет специальные шаблоны редакторов в папке /Views/Shared/EditorTemplates, поэтому мы добавляем такую папку в пример проекта и затем создаем внутри нее новый файл строго типизированного частичного представления по имени Role.cshtml. Содержимое этого файла приведено в примере ниже:

@model HelperMethods.Models.Role

@Html.DropDownListFor(m => m,
    new SelectList(Enum.GetNames(Model.GetType()), Model.ToString()))

Типом модели для этого представления является перечисление Role, и мы применяем вспомогательный метод Html.DropDownListFor(), чтобы создать элемент <select> с элементами <option> для значений перечисления. Конструктору SelectList передается дополнительное значение, указывающее выбранное значение, которое мы получаем из объекта модели представления. Метод DropDownListFor() и объект SelectList работают со значениями string, поэтому мы должны выполнить приведение значений перечисления и значения модели представления.

В случае применения любого шаблонизированного вспомогательного метода с целью генерации редактора для типа Role будет использоваться файл /Views/Shared/EditorTemplates/Role.cshtml, обеспечивая пользователю согласованное и удобное представление этого типа данных. Результат применения специального шаблона можно видеть на рисунке ниже:

Результат применения специального шаблона для перечисления Role

Порядок поиска шаблонов

Шаблон Role.cshtml работает, поскольку ASP.NET MVC Framework сначала ищет специальные шаблоны для заданного типа C# и только затем использует встроенные шаблоны. В действительности при поиске подходящего шаблона инфраструктура ASP.NET MVC Framework следует специфической последовательности:

  1. Шаблон, переданный вспомогательному методу - например, вызов Html.EditorFor(m => m.SomeProperty, "MyTemplate") приводит к использованию шаблона MyTemplate.

  2. Любой шаблон, указанный атрибутами метаданных, такими как UIHint.

  3. Шаблон, ассоциированный с любым типом данных, который указан с помощью атрибутов метаданных, таких как DataType.

  4. Любой шаблон, который соответствует имени класса .NET обрабатываемого типа данных.

  5. Встроенный шаблон String, если обрабатываемый тип данных является простым.

  6. Любой шаблон, который соответствует базовым классам типа данных.

  7. Если тип данных реализует интерфейс IEnumerable, будет использоваться встроенный шаблон Collection.

  8. Если все проверки завершились неудачно, будет применяться шаблон Object - результат действия правила о том, что формирование шаблонов не является рекурсивным.

Некоторые из перечисленных шагов полагаются на встроенные шаблоны, которые были описаны в предыдущей статье. На каждом этапе процесса поиска шаблона инфраструктура ASP.NET MVC Framework ищет шаблон по имени EditorTemplates/<имя> для вспомогательных методов редакторов или DisplayTemplates/<имя> для вспомогательных методов отображения. Для шаблона Role удовлетворяется условие на шаге 4 процесса поиска, т.к. был создан шаблон по имени Role.cshtml, который помещен в папку /Views/Shared/EditorTemplates.

Специальные шаблоны обнаруживаются с использованием того же алгоритма поиска, что и для обычных шаблонов; это означает, что мы можем создать специальный шаблон, специфичный для контроллера, поместить его в папку ~/Views/<Контроллер>/EditorTemplates и тем самым переопределить шаблоны, расположенные в папке ~/Views/Shared/EditorTemplates.

Создание обобщенного шаблона

Мы не ограничены созданием шаблонов, специфичных для типа. Можно, например, создать шаблон, который работает для всех перечислений, и затем указать, что этот шаблон должен выбираться, с использованием атрибута UIHint. Если вы взглянете на последовательность поиска выше, то заметите, что шаблоны, указанные с помощью атрибута UIHint, получают преимущество перед шаблонами, специфичными для типа. Для демонстрации этого в работе в папке /Views/Shared/EditorTemplates создан файл представления по имени Enum.cshtml. Содержимое этого файла приведено в примере ниже:

@model Enum

@Html.DropDownListFor(m => m, Enum.GetValues(Model.GetType())
    .Cast<Enum>()
    .Select(m =>
    {
        string enumVal = Enum.GetName(Model.GetType(), m);
        return new SelectListItem()
        {
            Selected = (Model.ToString() == enumVal),
            Text = enumVal,
            Value = enumVal
        };
    }))

Типом модели для этого шаблона является Enum, что позволяет работать с любым перечислением. В целях разнообразия для генерации строк, требуемых для создания элементов select и option, применялся LINQ (хотя это не входит в число требований обобщенного шаблона, просто мне нравится LINQ).

После этого можно применить атрибут UIHint, В рассматриваемом примере проекта определен родственный класс для метаданных, поэтому мы применяем атрибут UIHint к классу UserMetadata, как показано в примере ниже. (В качестве напоминания: определение этого класса находится в файле /Models/Metadata/UserMetadata.cs.)

using System;
using System.Web.Mvc;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace HelperMethods.Models
{
    [DisplayName("новый юзер")]
    public partial class UserMetaData
    {
        // ...

        [UIHint("Enum")]
        [DisplayName("Роль")]
        public Role Role { get; set; }
    }
}

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

Замена встроенных шаблонов

Когда вы создаете специальный шаблон, имя которого совпадает с именем одного из встроенных шаблонов, ASP.NET MVC Framework будет отдавать предпочтение специальной версии шаблона перед встроенной. В примере ниже приведено содержимое файла Boolean.cshtml, созданного в папке /Views/Shared/EditorTemplates. Это представление заменяет встроенный шаблон Boolean, который используется для визуализации значений bool и bool?:

@model bool?

@if (ViewData.ModelMetadata.IsNullableValueType && Model == null)
{
    @:(Правда) (Ложь) <b>(Не задано)</b>
}
else if (Model.Value)
{
    @:<b>(Правда)</b> (Ложь) (Не задано)
}
else
{
    @:(Правда) <b>(Ложь)</b> (Не задано)
}

В этом представлении отображаются все возможные значения, из которых выделяется то, что соответствует объекту модели. Результат применения показанного шаблона можно видеть на рисунке ниже:

Эффект от переопределения встроенного шаблона редактора

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

Пройди тесты
Лучший чат для C# программистов