Частичные представления ASP.NET MVC: префиксы входных имен

120

Предположим, у меня есть ViewModel вроде

public class AnotherViewModel
{
   public string Name { get; set; }
}
public class MyViewModel
{
   public string Name { get; set; }
   public AnotherViewModel Child { get; set; }
   public AnotherViewModel Child2 { get; set; }
}

В представлении я могу отрендерить партиал с помощью

<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

В частичной сделаю

<%= Html.TextBox("Name", Model.Name) %>
or
<%= Html.TextBoxFor(x => x.Name) %>

Однако проблема в том, что оба будут отображать name = "Name", в то время как мне нужно иметь name = "Child.Name" для правильной работы привязки модели. Или name = "Child2.Name", когда я визуализирую второе свойство с использованием того же частичного представления.

Как заставить мое частичное представление автоматически распознавать требуемый префикс? Я могу передать его как параметр, но это слишком неудобно. Это еще хуже, когда я хочу, например, отрендерить его рекурсивно. Есть ли способ визуализировать частичные представления с префиксом или, что еще лучше, с автоматическим повторным преобразованием вызывающего лямбда-выражения, чтобы

<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

автоматически добавит правильный "Ребенок". префикс к сгенерированным строкам имени / идентификатора?

Я могу принять любое решение, включая сторонние механизмы просмотра и библиотеки - я действительно использую Spark View Engine (я «решаю» проблему с помощью его макросов) и MvcContrib, но не нашел там решения. XForms, InputBuilder, MVC v2 - подойдет любой инструмент / идея, обеспечивающая эту функциональность.

В настоящее время я думаю о кодировании этого сам, но это кажется пустой тратой времени, я не могу поверить, что эта тривиальная вещь еще не реализована.

Может существовать множество ручных решений, и все они приветствуются. Например, я могу заставить свои частичные данные основываться на IPartialViewModel <T> {public string Prefix; Модель Т; }. Но я бы предпочел какое-то существующее / одобренное решение.

UPDATE: есть подобный вопрос без ответа здесь .

queen3
источник

Ответы:

110

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

using System.Web.Mvc.Html


 public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
    {
        string name = ExpressionHelper.GetExpressionText(expression);
        object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
        var viewData = new ViewDataDictionary(helper.ViewData)
        {
            TemplateInfo = new System.Web.Mvc.TemplateInfo
            {
                HtmlFieldPrefix = name
            }
        };

        return helper.Partial(partialViewName, model, viewData);

    }

и просто используйте его в своих представлениях следующим образом:

<%= Html.PartialFor(model => model.Child, "_AnotherViewModelControl") %>

и вы увидите, что все в порядке!

Махмуд Моравей
источник
17
Это будет неправильно для вложенного частичного рендеринга. Вам нужно добавить новый префикс к старому префиксу from helper.ViewData.TemplateInfo.HtmlFieldPrefixв форме{oldprefix}.{newprefix}
Иван Златев
@Mahmoud Ваш код отлично работает, но я обнаружил, что ViewData / ViewBag пуст, когда пришло время выполнить код в Partial. Я обнаружил, что помощник типа HtmlHelper <TModel> имеет новое свойство ViewData, которое скрывает базовую модель. Я заменил его new ViewDataDictionary(helper.ViewData)на new ViewDataDictionary(((HtmlHelper)helper).ViewData). Вы видите в этом проблемы?
Пэт Ньюэлл,
@IvanZlatev Спасибо. Пост поправлю после тестирования.
Махмуд Моравей
2
@IvanZlatev прав. Прежде чем установить имя, вы должны сделать этоstring oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc
2
Простое исправление для вложенных шаблонов - использовать HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName (name)
Аман Махаджан,
95

до сих пор я искал то же самое, что нашел этот недавний пост:

http://davybrion.com/blog/2011/01/prefixing-input-elements-of-partial-views-with-asp-net-mvc/

<% Html.RenderPartial("AnotherViewModelControl", Model.Child, new ViewDataDictionary
{
    TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Child1" }
})
%>
шучу
источник
3
Спасибо за ссылку, это безусловно лучший вариант из перечисленных здесь
БЖ
32
Очень поздно для этой вечеринки, но если вы сделаете этот подход, вы должны использовать ViewDataDictionaryконструктор, который принимает текущий ViewData, иначе вы потеряете ошибки состояния модели, данные проверки и т. Д.
bhamlin
9
основываясь на комментарии Бхамлина. вы также можете передать вложенный префикс, например (например, vb.net ex): Html.RenderPartial ("AnotherViewModelControl", Model.PropX, New ViewDataDictionary (ViewData) With {.TemplateInfo = New TemplateInfo () With {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix & ".PropX"}})
hubson bropa
1
Отличный ответ, +1. Может быть, стоит отредактировать его, чтобы учесть комментарий
@bhamlin
1
Обратите внимание: если вам нужно вернуть этот партиал с контроллера, вам также необходимо установить этот префикс. stackoverflow.com/questions/6617768/…
The Muffin Man
12

Мой ответ, основанный на ответе Махмуда Моравея, включая комментарий Ивана Златева.

    public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
    {
            string name = ExpressionHelper.GetExpressionText(expression);
            object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
            StringBuilder htmlFieldPrefix = new StringBuilder();
            if (helper.ViewData.TemplateInfo.HtmlFieldPrefix != "")
            {
                htmlFieldPrefix.Append(helper.ViewData.TemplateInfo.HtmlFieldPrefix);
                htmlFieldPrefix.Append(name == "" ? "" : "." + name);
            }
            else
                htmlFieldPrefix.Append(name);

            var viewData = new ViewDataDictionary(helper.ViewData)
            {
                TemplateInfo = new System.Web.Mvc.TemplateInfo
                {
                    HtmlFieldPrefix = htmlFieldPrefix.ToString()
                }
            };

        return helper.Partial(partialViewName, model, viewData);
    }

Изменить: ответ Мохамуда неверен для вложенного частичного рендеринга. Вам нужно добавить новый префикс к старому, только если это необходимо. Это не было ясно в последних ответах (:

asoifer1879
источник
6
Было бы полезно для других, если бы вы подробно рассказали, как приведенный выше вариант улучшает существующий ответ.
Ли
2
После жирной ошибки копирования и вставки эта отлично работала для ASP.NET MVC 5. +1.
Грег Бургхардт
9

Используя MVC2, вы можете добиться этого.

Вот строго типизированное представление:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcLearner.Models.Person>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Create</h2>

    <% using (Html.BeginForm()) { %>
        <%= Html.LabelFor(person => person.Name) %><br />
        <%= Html.EditorFor(person => person.Name) %><br />
        <%= Html.LabelFor(person => person.Age) %><br />
        <%= Html.EditorFor(person => person.Age) %><br />
        <% foreach (String FavoriteFoods in Model.FavoriteFoods) { %>
            <%= Html.LabelFor(food => FavoriteFoods) %><br />
            <%= Html.EditorFor(food => FavoriteFoods)%><br />
        <% } %>
        <%= Html.EditorFor(person => person.Birthday, "TwoPart") %>
        <input type="submit" value="Submit" />
    <% } %>

</asp:Content>

Вот строго типизированное представление для дочернего класса (которое должно храниться в подпапке каталога представления с именем EditorTemplates):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcLearner.Models.TwoPart>" %>

<%= Html.LabelFor(birthday => birthday.Day) %><br />
<%= Html.EditorFor(birthday => birthday.Day) %><br />

<%= Html.LabelFor(birthday => birthday.Month) %><br />
<%= Html.EditorFor(birthday => birthday.Month) %><br />

Вот контроллер:

public class PersonController : Controller
{
    //
    // GET: /Person/
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Index()
    {
        return View();
    }

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Create()
    {
        Person person = new Person();
        person.FavoriteFoods.Add("Sushi");
        return View(person);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(Person person)
    {
        return View(person);
    }
}

Вот пользовательские классы:

public class Person
{
    public String Name { get; set; }
    public Int32 Age { get; set; }
    public List<String> FavoriteFoods { get; set; }
    public TwoPart Birthday { get; set; }

    public Person()
    {
        this.FavoriteFoods = new List<String>();
        this.Birthday = new TwoPart();
    }
}

public class TwoPart
{
    public Int32 Day { get; set; }
    public Int32 Month { get; set; }
}

И источник вывода:

<form action="/Person/Create" method="post"><label for="Name">Name</label><br /> 
    <input class="text-box single-line" id="Name" name="Name" type="text" value="" /><br /> 
    <label for="Age">Age</label><br /> 
    <input class="text-box single-line" id="Age" name="Age" type="text" value="0" /><br /> 
    <label for="FavoriteFoods">FavoriteFoods</label><br /> 
    <input class="text-box single-line" id="FavoriteFoods" name="FavoriteFoods" type="text" value="Sushi" /><br /> 
    <label for="Birthday_Day">Day</label><br /> 
    <input class="text-box single-line" id="Birthday_Day" name="Birthday.Day" type="text" value="0" /><br /> 

    <label for="Birthday_Month">Month</label><br /> 
    <input class="text-box single-line" id="Birthday_Month" name="Birthday.Month" type="text" value="0" /><br /> 
    <input type="submit" value="Submit" /> 
</form>

Теперь все готово. Установите точку останова в действии Create Post Controller для проверки. Однако не используйте это со списками, потому что это не сработает. См. Мой вопрос об использовании EditorTemplates с IEnumerable для получения дополнительной информации.

Ник Ларсен
источник
Да, похоже. Проблема в том, что списки не работают (в то время как модели вложенных представлений обычно находятся в списках) и что это v2 ... который я не совсем готов использовать в производстве. Но все же приятно знать, что это будет то, что мне нужно ... когда оно появится (так что +1).
queen3
Я также столкнулся с этим на днях, matthidinger.com/archive/2009/08/15/… , возможно, вы захотите узнать, сможете ли вы выяснить, как расширить его для редакторов (хотя все еще в MVC2). Я потратил на это несколько минут, но продолжал сталкиваться с проблемами, потому что я еще не в состоянии выразить свое мнение. Может, у тебя получится лучше, чем у меня.
Ник Ларсен,
1
Полезная ссылка, спасибо. Не то чтобы я думаю, что здесь можно заставить EditorFor работать, потому что я полагаю, он не будет генерировать [0] индексов (готов поспорить, что он его еще не поддерживает). Одним из решений может быть отображение вывода EditorFor () в строку и ручная настройка вывода (добавление необходимых префиксов). Хотя грязный хакер. Гм! Я могу сделать метод расширения для Html.Helper (). UsePrefix (), который просто заменит name = "x" на name = "prefix.x" ... в MVC v1. Еще немного работы, но не так уж много. И Html.WithPrefix («префикс»). RenderPartial (), который работает в паре.
queen3
+1 за рекомендацию Html.EditorForметода рендеринга дочерней формы. Именно для этого и должны использоваться шаблоны редактора. Это должен быть ответ.
Грег Бургхардт
9

Это старый вопрос, но для тех, кто приезжает сюда в поисках решения, подумайте об использовании EditorFor, как это предлагается в комментарии в https://stackoverflow.com/a/29809907/456456 . Чтобы перейти от частичного представления к шаблону редактора, выполните следующие действия.

  1. Убедитесь, что ваше частичное представление привязано к ComplexType .

  2. Переместите частичное представление в подпапку EditorTemplates текущей папки представления или в папку Shared . Теперь это шаблон редактора.

  3. Измените @Html.Partial("_PartialViewName", Model.ComplexType)на @Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). Шаблон редактора необязателен, если это единственный шаблон для сложного типа.

Элементы ввода Html будут автоматически названы ComplexType.Fieldname.

Р. Шрерс
источник
8

PartailFor для asp.net Core 2 на случай, если кому-то понадобится.

    public static ModelExplorer GetModelExplorer<TModel, TResult>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TResult>> expression)
    {
        if (expression == null)
            throw new ArgumentNullException(nameof(expression));
        return ExpressionMetadataProvider.FromLambdaExpression(expression, htmlHelper.ViewData, htmlHelper.MetadataProvider);
    }

    public static IHtmlContent PartialFor<TModel, TResult>(this IHtmlHelper<TModel> helper, Expression<Func<TModel, TResult>> expression, string partialViewName, string prefix = "")
    {
        var modelExplorer = helper.GetModelExplorer(expression);
        var viewData = new ViewDataDictionary(helper.ViewData);
        viewData.TemplateInfo.HtmlFieldPrefix += prefix;
        return helper.Partial(partialViewName, modelExplorer.Model, viewData);
    }
Рахма Самарун
источник
3

Я также столкнулся с этой проблемой и после долгих мучений обнаружил, что было проще перепроектировать мои интерфейсы, так что мне не нужно было отправлять обратно вложенные объекты модели. Это вынудило меня изменить рабочие процессы интерфейса: теперь я требую, чтобы пользователь сделал в два этапа то, что я мечтал сделать на одном, но удобство использования и ремонтопригодность кода нового подхода теперь для меня важнее.

Надеюсь, это кому-то поможет.

Мэтт Кодж
источник
1

Вы можете добавить помощника для RenderPartial, который принимает префикс и помещает его в ViewData.

    public static void RenderPartial(this HtmlHelper helper,string partialViewName, object model, string prefix)
    {
        helper.ViewData["__prefix"] = prefix;
        helper.RenderPartial(partialViewName, model);
    }

Затем еще один помощник, который объединяет значение ViewData

    public static void GetName(this HtmlHelper helper, string name)
    {
        return string.Concat(helper.ViewData["__prefix"], name);
    }

а так в представлении ...

<% Html.RenderPartial("AnotherViewModelControl", Model.Child, "Child.") %>

в частичном ...

<%= Html.TextBox(Html.GetName("Name"), Model.Name) %>
Энтони Джонстон
источник
Да, это то, что я описал в комментарии к stackoverflow.com/questions/1488890/… . Еще лучшим решением будет RenderPartial, который также принимает лямбда, что легко реализовать. Тем не менее, спасибо за код, я думаю, это наиболее безболезненный и вечный подход.
queen3,
1
почему бы не использовать префикс helper.ViewData.TemplateInfo.HtmlFieldPrefix =? Я использую это в своем помощнике, и, похоже, он работает нормально.
ajbeaven
0

Как и вы, я добавляю свойство Prefix (строку) к своим ViewModels, которое я добавляю перед именами ввода, привязанными к моей модели. (ЯГНИ предотвращает нижеследующее)

Более элегантным решением может быть базовая модель представления, имеющая это свойство, и некоторые HtmlHelpers, которые проверяют, является ли модель представления производной от этой базы, и, если да, добавляют префикс к имени ввода.

Надеюсь, это поможет,

Дэн

Дэниел Эллиотт
источник
1
Хм, очень плохо. Я могу представить себе множество элегантных решений с настраиваемыми HtmlHelpers, проверяющими свойства, атрибуты, настраиваемый рендеринг и т. Д. Но, например, если я использую MvcContrib FluentHtml, могу ли я переписать их все для поддержки моих хаков? Странно, что никто об этом не говорит, как будто все просто используют плоские одноуровневые ViewModels ...
queen3
В самом деле, после длительного использования фреймворка и стремления к красивому, чистому коду представления, я думаю, что многоуровневая модель представления неизбежна. Например, модель просмотра корзины в модели просмотра заказа.
Дэниел Эллиотт,
0

Как насчет того, чтобы непосредственно перед вызовом RenderPartial сделать

<% ViewData["Prefix"] = "Child."; %>
<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

Тогда в вашей части у вас есть

<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>
Энтони Джонстон
источник
1
По сути, это то же самое, что и передача вручную (безопасно извлекать все модели представления из IViewModel с помощью «IViewModel SetPrefix (string)»), и это очень уродливо, если я не перепишу все помощники Html и RenderPartial, чтобы они автоматически управляли этот. Проблема не в том, как это сделать, я это уже решил; проблема в том, можно ли это сделать автоматически.
queen3
поскольку нет общего места, куда вы можете поместить код, который повлияет на всех помощников html, вы не сможете сделать это автоматически. посмотреть другой ответ ...
Энтони Джонстон,