Поскольку вспомогательные методы — обычные методы расширения класса HtmlHelper, принимающие произвольный набор параметров и возвращающие строковые значения, то создать свой вспомогательный метод не составляет труда. Например, в листинге 5.10 приведена заготовка вспомогательного метода, отображающего элемент управления для ввода даты.
Листинг 5.10. Вспомогательный метод для отображения элемента для ввода даты
using System.Web.Mvc;
public static class DataPickerHelper {
public static string DatePicker(this HtmlHelper html,
string id, string text)
{
}
}
Такой метод может быть использован в коде представления через синтаксис
<%= Html.DatePicker("id", "name") %>.
Непосредственная реализация этого метода может быть разной. Давайте посмотрим на то, как можно подойти к созданию такого простого элемента, как набор выпадающих списков для выбора даты.
Самый очевидный способ — сгенерировать разметку конкатенацией строк. Код для этого метода приведен в листинге 5.11.
Листинг 5.11. Реализация метода DatePickerc помощью конкатенации строк
using System.Web.Mvc;
using System.Text;
using System;
using System.Globalization;
public static class DataPickerHelper
{
public static string DatePicker(this HtmlHelper html, string id)
{
return DatePicker(html, id, String.Empty);
}
public static string DatePicker(this HtmlHelper html,
string id, string text)
{
StringBuilder sb = new StringBuilder();
if (!String.IsNullOrEmpty(text))
{
sb.Append("<div id=\"");
sb.Append (id);
sb.Append("\">");
sb.Append(text);
}
/* Day */
sb.Append("<select id=\"day_");
sb.Append(id);
sb.Append("\">");
sb.Append("<option></option>") ;
for (int i = 1; i <= 31; i++)
{
sb.Append("<option>");
sb.Append(i.ToString());
sb.Append("</option>");
sb.Append("</select>");
sb.Append(" ");
}
/* Month */
sb.Append("<select id=\"month_");
sb.Append(id);
sb.Append("\">");
sb.Append("<option></option>");
for (int i = 0; i <= 11; i++)
{
sb.Append("<option>");
sb.Append(DateTimeFormatInfo.CurrentInfo.MonthNames[i]);
sb.Append("</option>");
}
sb.Append("</select>");
sb.Append(" ");
/* Year */
sb.Append("<select id=\"year_");
sb.Append(id);
sb.Append("\">");
sb.Append("<option></option>");
for (int i = 1900; i <= DateTime.Now.Year; i++)
{
sb.Append("<option>");
sb.Append(i.ToString());
sb.Append("</option>");
}
sb.Append("</select>");
if (!String.IsNullOrEmpty(text))
{
sb.Append("</div>");
}
return sb.ToString();
}
}
Плюс решения, продемонстрированного в листинге 5.11, — простота реализации "в лоб", копированием кода из макета верстки. Минусы решения очевидны — поддержка такого кода сложна за счет необходимости работы с кодом, перемешанным с большим количеством строковых констант.
Упростить модификацию самой разметки и внесение в нее косметических изменений можно за счет размещения статических строковых констант в файлах ресурсов. Генерацию финальной разметки при этом можно выполнять, используя только методы форматирования строк, как это показано в листинге 5.12.
Листинг 5.12. Реализация метода DatePickerc помощью ресурсов
public static string DatePicker(this HtmlHelper html,
string id, string text)
{
return String.Format(Resources.DatePicker, id, text,
Resources.DaysOptions,
Resources.MonthsOptions, Resources.YearsOptions);
}
В приведенном фрагменте есть заметное преимущество — централизованное управление разметкой, возможность использования разной разметки для различных культур и разделение самой разметки и кода. Можно пойти дальше и создать дополнительную обертку над ресурсами, которая будет отвечать за небольшую модификацию фрагментов разметки. Кода в случае, приведенном в листинге 5.12, значительно меньше, чем в листинге 5.11, однако гибкость такого решения может быть недостаточной для вспомогательных методов, требующих частой модификации.
Достигнуть большего контроля над логикой и разметкой можно за счет использования дополнительной абстракции над созданием самой разметки, вынесением отдельных методов, генерирующих повторяющие элементы, и созданием тегов с помощью специального класса TagBuilder. В WebForms при создании контролов (Custom Controls) используется похожий подход.
TagBuilder активно применяется в расширениях, входящих в саму сборку System.Web.Mvc, в чем можно убедиться, посмотрев, например, на исходный код System.Web.MVC.SelectExtensions.
В листинге 5.13 приведен код, использующий больше абстракции, нежели предыдущие. Также в листинге 5.13 продемонстрирована простая концепция по восстановлению значений после отправки данных на сервер (метод Getvalue, выполняющий поиск в коллекции viewData, затем в параметрах запроса).
Листинг 5.13. Реализация метода DatePicker с дополнительными слоями абстракции
public static class DataPickerHelper
{
private static string DAY_PREFIX = "day_";
private static string MONTH_PREFIX = "month_";
private static string YEAR_PREFIX = "year_";
private static string ListItemToOption(SelectListItem item)
{
TagBuilder builder = new TagBuilder("option")
{
InnerHtml = HttpUtility.HtmlEncode(item.Text)
};
if (item.value != null)
builder.Attributes["value"] = item.Value;
if (item.Selected)
builder.Attributes["selected"] = "selected";
return builder.ToString(TagRenderMode.Normal);
}
private static string SelectList(string id,
List<SelectListItem> items)
{
StringBuilder listItemBuilder = new StringBuilder();
foreach (var item in items)
{
listItemBuilder.AppendLine(ListItemToOption(item));
}
TagBuilder tagBuilder = new TagBuilder("select")
{
InnerHtml = listItemBuilder.ToString()
};
tagBuilder.Attributes.Add("id", id);
tagBuilder.Attributes.Add("name", id);
return tagBuilder.ToString(TagRenderMode.Normal);
}
public static string DatePicker(this HtmlHelper html, string id)
{
return DatePicker(html, id, String.Empty);
}
public static string DatePicker(this HtmlHelper html,
string id, string text)
{
// buffer
StringBuilder sb = new StringBuilder();
// generate days
List<SelectListItem> days = new List<SelectListItem>();
string dayValue = GetValue(html, DAY_PREFIX + id);
for (int i = 0; i <= 31; i++)
{
days.Add(new SelectListItem
{
Text = (i == 0) ? String.Empty : i.ToString(),
Value = i.ToString(),
Selected = (dayValue == i.ToString())
});
}
sb.AppendLine(SelectList(DAY_PREFIX + id, days));
// generate months
List<SelectListItem> months = new List<SelectListItem>();
string monthValue = GetValue(html, MONTH_PREFIX + id);
for (int i = 0; i <= 12; i++)
{
months.Add(new SelectListItem
{
Text = (i == 0) ? String.Empty :
DateTimeFormatInfo.CurrentInfo.MonthNames[i — 1],
Value = i.ToString(),
Selected = (monthValue == i.ToString())
});
}
sb.AppendLine(SelectList(MONTH_PREFIX + id, months));
// generate years
List<SelectListItem> years = new List<SelectListItem>();
string yearValue = GetValue(html, YEAR_PREFIX + id);
for (int i = 1900; i <= DateTime.Now.Year; i++)
{
years.Add(new SelectListItem
{
Text = (i == 1900) ? String.Empty : i.ToString(),
Value = i.ToString(),
Selected = (yearValue == i.ToString())
});
}
sb.AppendLine(SelectList(YEAR_PREFIX + id, years));
// parent tag
if (!String.IsNullOrEmpty(text))
{
TagBuilder div = new TagBuilder("div");
div.Attributes.Add("id", id);
div.InnerHtml = text + sb.ToString();
sb = new StringBuilder(
div.ToString(TagRenderMode.Normal));
}
return sb.ToString();
}
private static string GetValue(HtmlHelper html, string id)
{
object o = null;
if (html.ViewData != null)
o = html.ViewData.Eval(id);
if (o == null)
o = html.ViewContext.RequestContext.HttpContext.Request.Params[id];
return (o == null) ? String.Empty : o.ToString();
}
}
В главе 2, в разд. "Использование элементов управления WebForms в MVC-приложениях” уже была продемонстрирована возможность применения серверных элементов управления в представлениях MVC. Как уже было сказано ранее, использование серверных элементов управления может быть оправдано, если не требуется интерактивное взаимодействие с соответствующим элементом, и он используется только для генерации разметки.
Несколько большую интерактивность взаимодействия с элементом управления можно реализовать за счет создания специального вспомогательного метода, взаимодействующего с этим элементом управления.
Сделать это можно просто, учитывая, что класс Webcontrol, которому наследуют классы конкретных элементов управления, содержит метод Render(), генерирующий разметку. Как раз метод Render() применяет инфраструктура WebForms для всех элементов управления на страницах. Пример использования элемента управления WebForms приведен в листинге 5.14.
Листинг 5.14. Пример использования класса Button во вспомогательном методе
public static class WebFormsHelper {
public static string WebFormsButton(this HtmlHelper html)
{
Button control = new Button { Text = "Web Forms Button" };
StringBuilder sb = new StringBuilder();
HtmlTextWriter htmlWriter = new HtmlTextWriter(
new StringWriter(sb));
control.RenderControl(htmlWriter);
return sb.ToString();
}
}
Листинг 5.14 демонстрирует концепцию использования серверных элементов управления: создать экземпляр класса элемента управления, задать его свойства, вызвать при необходимости методы, а затем сгенерировать разметку, которая будет возвращена вспомогательным методом.
В каких случаях подобный подход к использованию серверных элементов управления может быть оправдан? В случаях, когда у вас есть существующий элемент управления, отвечающий вашим требованиям, и для которого подобная обертка может быть удобной в использовании. Универсального рецепта для принятия решения, стоит ли использовать элемент управления подобным образом, — нет, однако имеет смысл задуматься в случае, когда для элемента управления приходится эмулировать серверные события. Зачастую в подобных ситуациях проще реализовать поддержку необходимой функциональности в самих вспомогательных методах.