Я совсем недавно начал работать с ASP.NET MVC и сразу удивился отсутствием элементов управления CheckBoxList и RadioButtonList в ASP.NET MVC 3. В этой статье я покажу решение данной проблемы с помощью создания своих контролов.Топик написан как альтернативный взгляд на создание элементов управления из этой статьи.
Введение
При написания элементов управления я ориентировался на исходные коды уже существующих DropDownList и ListBox чтобы сохранить стиль кода и аналогичность перегрузок. Это оградит пользователей от формирования данных каким-то новым способом, а также предоставит возможность прозрачно менять элементы управления CheckBoxList и RadioButtonList на DropDownList и ListBox, если возникнет такая необходимость.
Реализация
Элементы управления находятся в статическом классе, который состоит из множества перегрузок, а также внутренних и приватных вспомогательных методов. Я не буду описывать все вспомогательные методы, а разберу только главный из них, который использует все остальные.
Все перегрузки приводят в метод InputInternal, в котором происходит получение списка из ViewData (если мы его явно не передали через одну из перегрузок), после чего идет попытка получения заранее выбранных значений в списке.
var usedViewData = false;
if (inputList == null)
{
inputList = htmlHelper.GetInputData(fullName);
usedViewData = true;
}
var defaultValue = GetDefaultValue(htmlHelper, fullName, allowMultiple, usedViewData);
if (defaultValue != null)
{
inputList = GetListWithDefaultValue(inputList, defaultValue, allowMultiple);
}
После подготовки списка происходит генерация html-кода, который отображает список. Если есть какие-то ошибки в modelState, то к списку добавляется css класс для отображения ошибки во view через JavaScript.
var listItemBuilder = new StringBuilder();
if (inputList != null)
{
foreach (var item in inputList)
listItemBuilder.AppendLine(InputItemToHtml(item, allowMultiple, fullName));
}
var tagBuilder = new TagBuilder("ul")
{
InnerHtml = listItemBuilder.ToString()
};
tagBuilder.Attributes.Add("style", "padding:0");
tagBuilder.MergeAttributes(htmlAttributes, true /* replaceExisting */);
tagBuilder.GenerateId(fullName);
ModelState modelState;
if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
{
if (modelState.Errors.Count > 0)
tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
}
tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name));
return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
В конечном итоге у нас получается html-код такого вида (пример для списка с радиокнопками).
<ul id="RadioButtonTestItems" style="padding:0">
<li style="list-style-type:none;">
<label>
<input name="RadioButtonItems" type="radio" value="Value1"></input>
<span>Item1</span>
</label>
</li>
<li style="list-style-type:none;">
<label>
<input name="RadioButtonItems" type="radio" value="Value2"></input>
<span>Item2</span>
</label>
</li>
</ul>
Примеры использования
В качестве примера была создана модель Home с двумя свойствами: коллекция строк CheckBoxTestItems, в которой может быть несколько значений отмеченных чекбоксами (либо null если не выбран ни один из чекбоксов) и строка RadioButtonTestItems для хранения значения, которое было выбрано с помощью радиокнопок (также может оказаться null).
public class Home
{
public ICollection<string> CheckBoxTestItems { get; set; }
public string RadioButtonTestItems { get; set; }
}
Также в качестве примера было изменено содержание стандартного View для заглавной страницы. В нем создается форма с двумя списками типов CheckBoxList и RadioButtonList.
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
@Html.CheckBoxList("CheckBoxTestItems")
@Html.RadioButtonList("RadioButtonTestItems")
<p>
<input type="submit" value="Submit" />
</p>
}
В контроллере создан метод для генерации списков и добавления их в ViewBag. В этом контроллере я использую разные способы генерации списков, чтобы показать как можно больше возможностей элементов управления. В списке с чекбоксами в качестве данных выступает массив строк, а с помощью другого массива проставляются заранее отмеченные чекбоксы. В списке с радиокнопками я показал, как можно использовать значения, которые не являются копией текста рядом с контролом. Такая функциональность требуется, когда в качестве значений выступает ID какой-либо записи в базе данных.
private void GenerateListItems()
{
var checkBoxTestItems = new[] { "CheckBoxTestItem1", "CheckBoxTestItem2", "CheckBoxTestItem3", "CheckBoxTestItem4" };
var checkBoxTestItemsChecked = new[] { "CheckBoxTestItem2", "CheckBoxTestItem4" };
ViewBag.CheckBoxTestItems = new MultiSelectList(checkBoxTestItems, checkBoxTestItemsChecked);
var radioButtonTestItems = new List<SelectListItem>();
for (var radioButtonTestItemNumber = 1; radioButtonTestItemNumber <= 4; radioButtonTestItemNumber++)
{
var selectListItem = new SelectListItem
{
Text = "RadioButtonTestItem" + radioButtonTestItemNumber,
Value = "RadioButtonTestItemValue" + radioButtonTestItemNumber
};
radioButtonTestItems.Add(selectListItem);
}
ViewBag.RadioButtonTestItems = radioButtonTestItems;
}
И наконец действие Index. В нем вызывается описанный выше метод генерации списков, а также вывод из модели выбранных в списках значений после отправки формы.
public ActionResult Index()
{
GenerateListItems();
return View();
}
[HttpPost]
public ActionResult Index(Home model)
{
if (model.CheckBoxTestItems != null)
{
foreach (var checkBoxTestItem in model.CheckBoxTestItems)
ModelState.AddModelError("", string.Format(«Checked CheckBox item: {0}», checkBoxTestItem));
}
if (model.RadioButtonTestItems != null)
{
ModelState.AddModelError("", string.Format(«Checked SelectButton item: {0}», model.RadioButtonTestItems));
}
GenerateListItems();
return View(model);
}
Заключение
В конечном итоге получаем такой пример использования списков.

В заключение хочу сказать, что весь код написан несколько дней назад, так что возможны баги. Исходные коды доступны на GitHub.