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”。

如何使我的局部视图自动识别所需的前缀?我可以将其作为参数传递,但这太不方便了。例如,当我想递归渲染时,情况甚至更糟。有没有一种方法可以呈现带有前缀的局部视图,或者甚至更好的方法是自动重新构造调用的lambda表达式,以便

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

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

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

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

可能存在很多手动解决方案,欢迎所有人使用。例如,我可以强制我的局部对象基于IPartialViewModel <T> {public string Prefix; T型;}。但是我宁愿选择一些现有的/批准的解决方案。

更新:有没有回答类似的问题在这里

Answers:


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}
Ivan Zlatev 2012年

@Mahmoud您的代码效果很好,但是我发现在Partial中执行代码时ViewData / ViewBag为空。我发现HtmlHelper <TModel>类型的帮助器具有一个新属性ViewData,该属性隐藏了基本模型的属性。这样,我替换new ViewDataDictionary(helper.ViewData)new ViewDataDictionary(((HtmlHelper)helper).ViewData)。你觉得有什么问题吗?
帕特·纽维尔

@IvanZlatev谢谢。我将在测试后更正该帖子。
Mahmoud Moravej

2
@IvanZlatev是正确的。在设置名称之前,您应该做string oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc 2014年

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
以bhamlin的评论为基础。您还可以传递嵌套前缀,例如(很抱歉,这是vb.net ex):Html.RenderPartial(“ AnotherViewModelControl”,Model.PropX,New ViewDataDictionary(ViewData)With {.TemplateInfo = New TemplateInfo()With {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix&“ .PropX”}})
Hubson Bropa

1
好的答案,+ 1。也许值得编辑,以考虑到@bhamlin评论
ken2k 2014年

1
请注意,如果您需要从控制器返回此部分内容,则也需要在此设置该前缀。stackoverflow.com/questions/6617768/...
松饼人

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


6
如果您详细说明上述内容如何改善现有答案,对其他人会有所帮助。
Leigh 2013年

2
在出现繁琐的复制和粘贴错误之后,该脚本非常适合ASP.NET MVC5。+1。
Greg Burghardt

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()做扩展方法,它将在MVC v1中将name =“ x”替换为name =“ prefix.x”...。仍然需要一些工作,但不是很多。与Html.WithPrefix(“ prefix”)。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 Input元素将自动命名ComplexType.Fieldname


8

如果有人需要,请为asp.net Core 2使用PartailFor

    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

我也遇到了这个问题,经过一番痛苦之后,我发现重新设计接口更加容易,这样我就不需要回发嵌套的模型对象。这迫使我改变了界面工作流程:确定我现在要求用户分两步完成我梦dream以求的工作,但是新方法的可用性和代码可维护性现在对我来说具有更大的价值。

希望这会有所帮助。


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,它也需要lambda,这很容易实现。不过,感谢您的代码,我想这是最轻松,最永恒的方法。
queen3

1
为什么不使用helper.ViewData.TemplateInfo.HtmlFieldPrefix =前缀?我在帮手中使用它,似乎工作正常。
2011年

0

像您一样,我将Prefix属性(字符串)添加到ViewModels中,并将其追加到模型绑定的输入名称之前。(YAGNI防止出现以下情况)

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

希望能有所帮助,


1
嗯,太糟糕了。我可以想象出许多优雅的解决方案,其中包括自定义的HtmlHelpers检查属性,属性,自定义的渲染等。但是,例如,如果我使用MvcContrib FluentHtml,是否重写它们全部以支持我的黑客?奇怪的是没有人谈论它,好像每个人都只是使用平面的单级ViewModels一样
queen3 09/09/28

确实,在使用框架任何时间并努力获得清晰的视图代码之后,我认为分层的ViewModel是不可避免的。例如,订单视图模型中的购​​物篮视图模型。
Daniel Elliott

0

在调用RenderPartial之前怎么样

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

然后你有部分

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

1
这基本上与手动传递相同(使用“ IViewModel SetPrefix(string)”从IViewModel派生所有视图模型是安全的类型),并且非常丑陋,除非我重写所有的HTML帮助器和RenderPartial,以便它们自动管理这个。问题不在于怎么做,我已经解决了。问题是,它可以自动完成吗?
queen3

由于没有常见的地方,您可以放置​​将影响所有html助手的代码,因此您将无法自动执行此操作。查看其他答案...
Anthony Johnston
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.