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();