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

Предположим, что у меня есть 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) %>

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

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

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

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

ОБНОВЛЕНИЕ: есть аналогичный вопрос без ответа здесь.

Ответ 1

Вы можете расширить класс помощника 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") %>

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

Ответ 3

Используя 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.

Ответ 4

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

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

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

Ответ 5

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

  • Убедитесь, что ваш частичный вид связан с ComplexType.

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

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

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

Ответ 6

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

Надеюсь, это поможет некоторым.

Ответ 7

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

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

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

    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) %>

Ответ 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);
        helper.ViewData.TemplateInfo.HtmlFieldPrefix += prefix;
        return helper.Partial(partialViewName, modelExplorer.Model, helper.ViewData);
    }

Ответ 9

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

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

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

Дан

Ответ 10

Как насчет того, как вы вызываете RenderPartial, вы делаете

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

Затем в частичном у вас есть

<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>