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