126

假设我有 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"。

如何让我的局部视图自动识别所需的前缀?我可以将它作为参数传递,但这太不方便了。例如,当我想以递归方式渲染它时,情况就更糟了。有没有办法用前缀渲染部分视图,或者更好的是,自动重新调整调用 lambda 表达式,以便

<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

将自动添加正确的“孩子”。生成的名称/ID字符串的前缀?

我可以接受任何解决方案,包括 3-rd 方视图引擎和库 - 我实际上使用 Spark View Engine(我使用它的宏“解决”问题)和 MvcContrib,但在那里没有找到解决方案。XForms、InputBuilder、MVC v2 - 任何提供此功能的工具/见解都会很棒。

目前我正在考虑自己编写代码,但这似乎是在浪费时间,我不敢相信这些微不足道的东西还没有实现。

可能存在很多手动解决方案,欢迎所有这些解决方案。例如,我可以强制我的部分基于 IPartialViewModel<T> { public string Prefix; T型;}。但我更喜欢一些现有/批准的解决方案。

更新:这里有一个类似的问题没有答案。

4

11 回答 11

112

您可以通过以下方式扩展 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") %>

你会看到一切正常!

于 2011-06-09T11:38:35.700 回答
98

到目前为止,我一直在寻找与我最近的帖子相同的东西:

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" }
})
%>
于 2011-01-26T17:20:12.620 回答
12

我的回答基于 Mahmoud Moravej 的回答,包括 Ivan Zlatev 的评论。

    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 的答案对于嵌套的部分渲染是不正确的。只有在必要时,您才需要将新前缀附加到旧前缀。这在最新的答案中并不清楚(:

于 2013-09-06T18:02:28.637 回答
10

使用 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 一起使用的问题。

于 2009-09-28T19:51:49.930 回答
10

这是一个老问题,但对于任何来到这里寻找解决方案的人,请考虑使用,如https://stackoverflow.com/a/29809907/456456EditorFor中的评论中所建议的那样。要从局部视图移动到编辑器模板,请执行以下步骤。

  1. 验证您的局部视图是否绑定到ComplexType

  2. 将您的局部视图移动到当前视图文件夹的子文件夹EditorTemplates或文件夹Shared。现在,它是一个编辑器模板。

  3. 更改@Html.Partial("_PartialViewName", Model.ComplexType)@Html.EditorFor(m => m.ComplexType, "_EditorTemplateName")。如果编辑器模板是复杂类型的唯一模板,则它是可选的。

Html Input 元素将自动命名ComplexType.Fieldname

于 2016-04-08T09:03:14.423 回答
9

用于 asp.net Core 2 的ParttailFor,以防有人需要。

    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);
    }
于 2017-09-19T19:17:05.003 回答
3

我也遇到了这个问题,在经历了很多痛苦之后,我发现重新设计我的界面更容易,这样我就不需要回发嵌套的模型对象了。这迫使我改变我的界面工作流程:当然我现在要求用户分两步完成我梦想做的事情,但是新方法的可用性和代码可维护性现在对我来说更有价值。

希望这会有所帮助。

于 2009-09-29T07:26:13.193 回答
3

如此处所述:https ://stackoverflow.com/a/58943378/3901618 - 对于 ASP.NET Core - 您可以使用部分标记助手。

<partial name="AnotherViewModelControl" for="Child" />
<partial name="AnotherViewModelControl" for="Child2" />

它生成所有必需的名称前缀。

于 2021-04-05T09:47:28.093 回答
2

您可以为 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) %>
于 2009-09-29T07:59:32.307 回答
1

像你一样,我将 Prefix 属性(一个字符串)添加到我的 ViewModels 中,我在我的模型绑定输入名称之前附加了它。(YAGNI 防止以下情况)

一个更优雅的解决方案可能是具有此属性的基本视图模型和一些 HtmlHelper,它们检查视图模型是否源自此基础,如果是,则将前缀附加到输入名称。

希望有帮助,

于 2009-09-28T19:39:56.273 回答
1

在你打电话给 RenderPartial 之前怎么样

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

然后在你的部分你有

<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>
于 2009-09-28T19:47:39.600 回答