【问题标题】:ASP.NET MVC partial views: input name prefixesASP.NET MVC 部分视图:输入名称前缀
【发布时间】:2010-12-02 02:42:27
【问题描述】:

假设我有类似的 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 方视图引擎和库 - 我实际上使用 Spark View Engine(我使用它的宏“解决”问题)和 MvcContrib,但在那里没有找到解决方案。 XForms、InputBuilder、MVC v2 - 任何提供此功能的工具/见解都会很棒。

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

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

更新:有一个类似的问题没有答案here

【问题讨论】:

    标签: asp.net-mvc spark-view-engine mvccontrib


    【解决方案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") %>
    

    你会发现一切正常!

    【讨论】:

    • 这对于嵌套的部分渲染是不正确的。您需要以{oldprefix}.{newprefix} 的形式将新前缀附加到来自helper.ViewData.TemplateInfo.HtmlFieldPrefix 的旧前缀
    • @Mahmoud 您的代码运行良好,但我发现在 Partial 中执行代码时 ViewData/ViewBag 是空的。我发现 HtmlHelper 类型的助手有一个新属性 ViewData,它隐藏了基本模型的。有了这个,我用new ViewDataDictionary(((HtmlHelper)helper).ViewData)替换了new ViewDataDictionary(helper.ViewData)。你觉得这有什么问题吗?
    • @IvanZlatev 谢谢。我会在测试后更正帖子。
    • @IvanZlatev 是正确的。在设置名称之前,你应该做string oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
    • 嵌套模板的一个简单修复方法是使用 HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name)
    【解决方案2】:

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

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

    【讨论】:

    • 感谢您的链接,这是迄今为止列出的最佳选择
    • 这个派对超级晚了,但是如果你这样做,你应该使用ViewDataDictionary 构造函数,它接受当前的ViewData,否则你会丢失模型状态错误、验证数据等.
    • 以 bhamlin 的评论为基础。您还可以传递嵌套前缀,例如(sry this is vb.net ex): Html.RenderPartial("AnotherViewModelControl", Model.PropX, New ViewDataDictionary(ViewData) With {.TemplateInfo = New TemplateInfo() With {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix & ".PropX"}})
    • 很好的答案,+1。也许值得编辑它以考虑@bhamlin 评论
    • 请注意,如果你需要从控制器返回这个部分,你也需要在那里设置这个前缀。 stackoverflow.com/questions/6617768/…
    【解决方案3】:

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

    【讨论】:

    • 如果您详细说明上述如何在现有答案的基础上进行改进,将会对其他人有所帮助。
    • 在一个粗心的复制粘贴错误之后,这个对于 ASP.NET MVC 5 非常有效。+1。
    【解决方案4】:

    使用 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)。
    • 前几天我也遇到了这个问题,matthidinger.com/archive/2009/08/15/…,你可能想看看你是否可以为编辑器扩展它(尽管仍在 MVC2 中)。我花了几分钟的时间,但一直遇到问题,因为我还没有达到表达水平。也许你可以比我做得更好。
    • 一个有用的链接,谢谢。并不是说我认为可以让 EditorFor 在这里工作,因为我想它不会生成 [0] 索引(我敢打赌它根本不支持它)。一种解决方案是将 EditorFor() 输出呈现为字符串并手动调整输出(附加所需的前缀)。不过,这是一个肮脏的黑客行为。嗯!我可以为 Html.Helper().UsePrefix() 做扩展方法,它只会在 MVC v1 中将 name="x" 替换为 name="prefix.x"...。仍然有一些工作,但没有那么多。和 Html.WithPrefix("prefix").RenderPartial() 成对工作。
    • +1 推荐使用Html.EditorFor 方法来呈现子表单。这正是编辑器模板应该用于的用途。这应该是答案。
    【解决方案5】:

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

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

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

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

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

    【讨论】:

      【解决方案6】:

      ParttailFor 用于 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);
          }
      

      【讨论】:

        【解决方案7】:

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

        希望这会有所帮助。

        【讨论】:

          【解决方案8】:

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

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

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

          【讨论】:

            【解决方案9】:

            您可以为 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,它也采用 lambda,这很容易实现。不过,感谢代码,我想这是最轻松、最永恒的方法。
            • 为什么不使用 helper.ViewData.TemplateInfo.HtmlFieldPrefix = 前缀?我在我的助手中使用它,它似乎工作正常。
            【解决方案10】:

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

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

            希望对你有帮助,

            【讨论】:

            • 嗯,太糟糕了。我可以想象许多优雅的解决方案,使用自定义 HtmlHelpers 检查属性、属性、自定义渲染等……但是例如,如果我使用 MvcContrib FluentHtml,我是否要重写所有这些以支持我的 hack?奇怪的是没有人谈论它,好像每个人都只是使用平面单级ViewModels......
            • 确实,在使用框架任意长的时间并努力获得漂亮干净的视图代码之后,我认为分层的 ViewModel 是不可避免的。例如 Order ViewModel 中的 Basket ViewModel。
            【解决方案11】:

            在你调用 RenderPartial 之前怎么样

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

            那么在你的部分你有

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

            【讨论】:

            • 这与手动传递它基本相同(使用“IViewModel SetPrefix(string)”从 IViewModel 派生所有视图模型是类型安全的),并且非常难看,除非我重写所有 Html helpers 和 RenderPartial 以便他们自动管理它。问题不在于怎么做,我已经解决了;问题是,能不能自动完成。
            • 因为没有共同的地方可以放置会影响所有 html 帮助程序的代码,您将无法自动执行此操作。查看其他答案...
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2010-11-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多