Часткові перегляди 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) %>

автоматично додасть правильне "Дитина". префікс до згенерованих рядків імені / id?

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

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

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

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
Це буде неправильним для вкладеного часткового візуалізації. Вам потрібно додати новий префікс до старого префіксу 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
Дякуємо за посилання, це далеко не найкращий варіант, перелічений тут
BZ

32
Супер пізно для цієї партії, але якщо ви скористаєтеся таким підходом, вам слід скористатися ViewDataDictionaryконструктором, який приймає поточний ViewData, або ви втратите помилки стану моделі, дані валідації тощо
bhamlin

9
спираючись на коментар Бемліна. Ви також можете пропустити вкладений префікс, наприклад (sry це vb.net, наприклад): Html.RenderPartial ("AnotherViewModelControl", Model.PropX, New ViewDataDictionary (ViewData) з {.TemplateInfo = New TemplateInfo () З {.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 для перевірки. Однак не використовуйте це зі списками, оскільки це не буде працювати. Дивіться моє запитання щодо використання 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

Дійсно, використовуючи рамку протягом будь-якого проміжку часу і прагнучи до приємного чистого коду перегляду, я думаю, що багатоярусний ViewModel неминучий. Наприклад, ViewModel Basket у програмі ViewModel для замовлення.
Даніель Елліотт

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.