Частичные представления 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) %>
автоматически добавит правильный "ребенок". префикс к сгенерированным строкам name / id?
Я могу принять любое решение, включая сторонние механизмы просмотра и библиотеки - я фактически использую Spark View Engine (я "решаю" проблему с помощью его макросов) и MvcContrib, но не нашел там решения. XForms, InputBuilder, MVC v2 - любой инструмент / понимание, которые предоставляют эту функциональность, будут отличными.
В настоящее время я думаю о кодировании этого самостоятельно, но это кажется пустой тратой времени, я не могу поверить, что этот тривиальный материал еще не реализован.
Может существовать множество ручных решений, и все они приветствуются. Например, я могу сделать так, чтобы мои партиалы основывались на IPartialViewModel
ОБНОВЛЕНИЕ: есть подобный вопрос без ответа здесь.
11 ответов
Вы можете расширить вспомогательный класс 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") %>
и вы увидите, что все в порядке!
До сих пор я искал то же самое, что нашел в этом недавнем посте:
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" }
})
%>
Мой ответ основан на ответе Махмуда Моравея, включая комментарий Ивана Златева.
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);
}
Редактировать: ответ Мохамуда является неправильным для вложенного частичного рендеринга. Вам необходимо добавить новый префикс к старому префиксу, только если это необходимо. Это было не ясно в последних ответах (:
Это старый вопрос, но для тех, кто прибывает сюда в поисках решения, подумайте об использовании EditorFor
, как это предлагается в комментарии в /questions/33198182/poluchenie-znachenij-iz-vlozhennogo-slozhnogo-obekta-kotoryij-peredaetsya-v-chastichnoe-predstavlenie/33198204#33198204. Чтобы перейти от частичного представления к шаблону редактора, выполните следующие действия.
Убедитесь, что ваше частичное представление связано с ComplexType.
Переместите частичное представление в подпапку EditorTemplates текущей папки представления или в папку Shared. Теперь это шаблон редактора.
+ Изменить
@Html.Partial("_PartialViewName", Model.ComplexType)
в@Html.EditorFor(m => m.ComplexType, "_EditorTemplateName")
, Шаблон редактора является необязательным, если это единственный шаблон для сложного типа.
Элементы ввода HTML будут автоматически названы ComplexType.Fieldname
,
Используя 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 для получения дополнительной информации об этом.
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);
}
Как указано здесь: /questions/33198182/poluchenie-znachenij-iz-vlozhennogo-slozhnogo-obekta-kotoryij-peredaetsya-v-chastichnoe-predstavlenie/55329348#55329348 - для ASP.NET Core - вы можете использовать помощник частичного тега.
<partial name="AnotherViewModelControl" for="Child" />
<partial name="AnotherViewModelControl" for="Child2" />
Он генерирует все необходимые префиксы имен.
Я также столкнулся с этой проблемой, и после большой боли я обнаружил, что мне легче было перепроектировать мои интерфейсы, так что мне не нужно было отправлять вложенные объекты модели. Это вынудило меня изменить рабочие процессы моего интерфейса: конечно, теперь я требую, чтобы пользователь выполнил два шага, о которых я мечтал, на одном, но удобство и удобство поддержки нового подхода для кода теперь важнее для меня.
Надеюсь, это поможет некоторым.
Вы можете добавить помощника для 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) %>
Как насчет того, чтобы позвонить в RenderPartial?
<% ViewData["Prefix"] = "Child."; %>
<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>
Тогда в вашем частичном у вас есть
<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>
Как и вы, я добавляю свойство Prefix (строку) в мои ViewModels, которые я добавляю перед входными именами, привязанными к моей модели. (ЯГНИ мешает ниже)
Более элегантным решением может быть модель базового представления, имеющая это свойство, и некоторые HtmlHelpers, которые проверяют, является ли модель представления производной от этой базы, и, если это так, добавляют префикс к имени входа.
Надеюсь, это поможет,
Дэн