Пользовательский помощник CheckBoxListFor Html. Как привязать к свойству viewmodel?

Я создаю собственный помощник Html для обработки списка флажков. Я знаю, что в Интернете есть масса информации по этой теме, но я мало что видел, пытаясь сгенерировать HTML, как показано ниже.

Желаемый визуализированный HTML

<ul>
    <li>
         <label>
             <input type="checkbox" name="Genres" value="SF" />
             Science Fiction
         </label>
    </li>
    <li>
         <label>
             <input type="checkbox" name="Genres" value="HR" />
             Horror
         </label>
    </li>
    <!-- more genres -->
</ul>

Я использую отдельные флажки значения, а не только логические значения, потому что хочу воспользоваться преимуществом того факта, что при публикации формы я получаю список выбранных значений, разделенных запятыми. . Например, если бы были выбраны одновременно Sci-Fi и Horror, я бы получил "SF,HR".

Посмотреть модель

public class MovieViewModel
{
    public string Name { get;set; }
    public string Year { get;set; }
    public IEnumerable<string> Genres { get;set; }
    public IEnumerable<SelectListItem> GenreOptions { get;set; }
}

Вид

На мой взгляд, я хотел бы в основном это сделать:

@Html.EditorFor(model => model.Name)
@Html.EditorFor(model => model.Year)
@Html.CheckBoxListFor(model => model.Genres, Model.GenreOptions)

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

Итак, я начал создавать собственный помощник Html, но у меня нет большого опыта работы с ними, и здесь мне нужна помощь:

    public static MvcHtmlString CheckboxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        IEnumerable<SelectListItem> items)
    {
        if (items == null) return null;

        var itemsList = items.ToList();
        if (!itemsList.Any()) return null;

       // How do I get "Genres" name from the passed expression from viewmodel, without passing the hardcoded string "genres"?
        var checkboxGroupName = expression.what????

        var sb = new StringBuilder();
        sb.Append("<ul>");

        foreach (var item in itemsList)
        {
            var checkbox = $"<input type=\"checkbox\" name=\"{checkboxGroupName}\" value=\"{item.Value}\" />";
            sb.Append($"<li class=\"checkbox\"><label>{checkbox} {HttpUtility.HtmlEncode(item.Text)}</label></li>");
        }

        sb.Append("</ul>");

        return MvcHtmlString.Create(sb.ToString());
    }

Как вы можете видеть в комментарии выше, я не знаю, как динамически назначать имя свойства модели представления «Жанры» атрибуту name флажков без жесткого его кодирования. Как мне получить это из model => model.Genres выражения?


person Jiveman    schedule 26.01.2017    source источник
comment
В качестве примечания, вы не принимаете во внимание отмеченное состояние флажков (все они будут не отмечены)   -  person    schedule 27.01.2017
comment
ExpressionHelper.GetExpressionText(expression) работал хорошо, как и ответ @Ehsan Sajjad ниже! Спасибо! Однако как насчет сценариев вложенных моделей просмотра? Как мне получить что-то вроде <input name="Movie.Genres" />? И да, вы правы насчет ранее существовавшего состояния флажков. Думаю, я еще не зашел так далеко. Есть указатели?   -  person Jiveman    schedule 27.01.2017
comment
Ответ Эхсана Саджада для этого не работает - использование @CheckboxListFor(m => m.ComplexProperty.SomeProperty, ....) вернет "SomeProperty". Вам нужно использовать код из моего первого комментария, который даст "ComplexProperty.SomePropery"   -  person    schedule 27.01.2017
comment
И вам нужно многое изменить, чтобы получить правильную двустороннюю привязку модели. Сейчас нет времени, но я отвечу позже. И хотите ли вы также иметь возможность проводить любую проверку (например, установить хотя бы один флажок)   -  person    schedule 27.01.2017
comment
Re: вложенные модели просмотра, я больше думал о таких ситуациях, как @Html.EditorFor(m => m.Movie), где Movie имеет тип MovieViewModel, в котором есть жанры, как описано в моем OP. Не уверен, хорошо ли я описываю все дело с вложением. И да, я полагаю, мне также нужно будет изучить валидацию. С нетерпением жду вашего подхода к этому вопросу!   -  person Jiveman    schedule 27.01.2017
comment
В этом нет смысла - чтобы привязать к вам MovieViewModel, ваше выражение должно быть m=> m.Genres. Если у вас есть родительская модель представления, содержащая свойство MovieViewModel Movies, тогда выражение будет m => m.Movies.Genres (в этом случае ExpressionHelper.GetExpressionText(expression) будет генерировать name="Movies.Genres" (но metadata.PropertyName не будет)   -  person    schedule 27.01.2017
comment
Мм понятно. Что, если бы я использовал пользовательский EditorTemplate для MovieViewModel и ссылался на него в родительском представлении как на @Html.EditorFor(m => m.Movie)? В этом случае я бы не хотел помещать жанры непосредственно в родительский вид, верно, но полагался бы на шаблон редактора MovieViewModel для его рендеринга.   -  person Jiveman    schedule 27.01.2017
comment
Давайте продолжим это обсуждение в чате.   -  person    schedule 27.01.2017


Ответы (2)


Чтобы получить имя свойства, используйте

var name = ExpressionHelper.GetExpressionText(expression);

что даст полное имя, например @CheckboxListFor(m => m.Movies.Genres, ....) вернет Movies.Genres.

Но чтобы учесть случаи, когда вы используете пользовательскую EditorTemplate для вашей модели (которая является свойством родительской модели), вам также необходимо получить `HtmlFieldPrefix и, если он существует, добавить его имя.

Текущий код не дает вам правильной привязки, например, если Genres содержит значения, соответствующие свойству Value SelectListItem, соответствующий флажок должен быть установлен при визуализации представления. Ваш код должен быть

public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> items)
{
    if (items == null)
    {
        throw new ArgumentException("...");
    }
    var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    var model = metadata.Model as IEnumerable<string>;
    if (model == null)
    {
        throw new ArgumentException("...");
    }
    // Get the property name
    var name = ExpressionHelper.GetExpressionText(expression);
    // Get the prefix in case using a EditorTemplate for a nested property
    string prefix = htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix;
    if (!String.IsNullOrEmpty(prefix))
    {
        name = String.Format("{0}.{1}", prefix, name);
    }
    StringBuilder html = new StringBuilder();
    foreach (var item in items)
    {
        StringBuilder innerHtml = new StringBuilder();
        TagBuilder checkbox = new TagBuilder("input");
        checkbox.MergeAttribute("type", "checkbox");
        checkbox.MergeAttribute("name", name);
        checkbox.MergeAttribute("value", item.Value);
        if (model.Contains(item.Value))
        {
            checkbox.MergeAttribute("checked", "checked");
        }
        innerHtml.Append(checkbox.ToString());
        TagBuilder text = new TagBuilder("span");
        text.InnerHtml = item.Text;
        innerHtml.Append(text.ToString());
        TagBuilder label = new TagBuilder("label");
        label.InnerHtml = innerHtml.ToString();
        TagBuilder li = new TagBuilder("li");
        li.AddCssClass("checkbox");
        li.InnerHtml = label.ToString();
        html.Append(li);
    }
    TagBuilder ul = new TagBuilder("ul");
    ul.InnerHtml = html.ToString();
    return MvcHtmlString.Create(ul.ToString());
}
person Community    schedule 27.01.2017
comment
Работает отлично. Благодарим за включение полного решения! - person Jiveman; 27.01.2017

Вы можете получить имя свойства, переданного в Html Helper, из параметра типа Expression<Func<TModel,TProperty>>, который передается с помощью ModelMetaData со следующими строками кода:

var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var checkboxGroupName = metadata.PropertyName;

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

person Ehsan Sajjad    schedule 26.01.2017
comment
Спасибо, это сработало! Читайте больше по ссылке, которую вы предоставили, чтобы выяснить, как получить префикс в сценариях модели вложенных представлений, таких как <input name="Movie.Genres" />. - person Jiveman; 27.01.2017