Частичные представления 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: есть подобный вопрос без ответа здесь .

Ответы:


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

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


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, вы не сможете сделать это автоматически. посмотреть другой ответ ...
— Энтони Джонстон,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.