【问题标题】:Binding conflict between a property named Title in my Model and View.Title in my View (in MVC)我的模型中名为 Title 的属性与我的视图中的 View.Title 之间的绑定冲突(在 MVC 中)
【发布时间】:2011-05-21 11:49:15
【问题描述】:

我的模型包含一个名为Title 的属性,在我的Create 视图中,我使用ViewBag.Title 设置页面标题。

这会产生以下问题:Html.Editor 生成的表单将显示来自ViewBag.Title 的文本,而不是模型的Title 值。

我发现的唯一解决方法是先调用Html.Editor,然后设置View.Title

谁有更好的解决方案?

编辑 1:我正在使用 MVC 3。

编辑 2:这是我的DisplayTemplates/Object.cshtml

@model dynamic
@using Iconum.VS10CS040.Library.Web.MVC3.Helpers

@if (ViewData.TemplateInfo.TemplateDepth > 1) {
    <span class="editor-object simple">@ViewData.ModelMetadata.SimpleDisplayText</span>
} else {
    foreach (var prop in ViewData.ModelMetadata.Properties.Where(
            pm => 
                pm.ShowForEdit 
                && !ViewData.TemplateInfo.Visited(pm)      
                && pm.ModelType != typeof(System.Data.EntityState)
                && !pm.IsComplexType             
            )
        ) 
        {
        if (prop.HideSurroundingHtml) {
            <text>@Html.Editor(prop.PropertyName)</text>
        } else {
            string css = "";
            if (prop.Model != null && prop.Model.GetType() != null)
            {
                css += " " + prop.Model.GetType().ToString().ToLower().Replace('.', '-');
            }
            if (prop.DataTypeName != null)
            {
                css += " " + prop.DataTypeName.ToLower();
            }
            if (prop.IsRequired && prop.ModelType.FullName != "System.Boolean")
            {
                css += " required";
            }

            <div class="editor-container @css">
                 <div class="editor-label">
                    @if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
                    {
                        // Use LabelWithForThatMatchesTheIdOfTheInput instead of Label because of a bug (fixed in MVC 3)
                       @Html.LabelWithForThatMatchesTheIdOfTheInput(prop.PropertyName)
                    }
                    @if (prop.IsRequired && prop.ModelType.FullName != "System.Boolean")
                    {
                        @Html.Raw(" <span class=\"required\">*<span>");
                    }
                </div>
                <div class="editor-field">
                    @* This the line that causes my problem *@
                    @Html.Editor(prop.PropertyName) 
                    @Html.ValidationMessage(prop.PropertyName)
                </div>
            </div>
        }
        } //foreach

    // Loop though all items in the Model with an TemplateHint (UIHint)
    foreach (var prop in ViewData.ModelMetadata.Properties.Where(
           pm => pm.ShowForEdit
           && !ViewData.TemplateInfo.Visited(pm)
           && pm.ModelType != typeof(System.Data.EntityState)
           && !pm.IsComplexType
           && pm.TemplateHint != null
           && (
            pm.TemplateHint == "jWYSIWYG0093"
            ||
            pm.TemplateHint == "jQueryUIDatepicker"
            ||
            pm.TemplateHint == "CKEditor"
           )
           )
       )
    {
        // TODO: check for duplicate js file includes
        @Html.Editor(prop.PropertyName, prop.TemplateHint + "-Script")
    }    

}

【问题讨论】:

  • 仅供参考:同样的“过度激进”绑定发生在 DisplayFor 中。
  • 什么是View.Title?你的意思是ViewBag.Title?您使用的是哪个版本的 ASP.NET MVC?哪个视图引擎?
  • 是的,这是关于 ViewBag.Title 的,看起来像是绑定到这个变量的编辑器。
  • 您能否发布一个允许重现该问题的示例代码。我无法重现它。 EditorFor 总是使用模型的属性。
  • 不,这不是关于 EditorFor(),这是关于 Html.Editor(string) 方法。我用它来生成表单,模型属性名称(“Title”)作为参数传递,同名的值应该在 ViewBag 中定义。

标签: asp.net-mvc razor model conflict


【解决方案1】:

我建议使用EditorFor 而不是Editor

Html.EditorFor(x => x.Title)

代替:

Html.Editor("Title")

这样,视图不仅可以利用您的视图模型,而且在这种情况下它的行为也符合预期。

ASP.NET MVC 3.0 RTM (Razor) 示例:

型号:

public class MyViewModel
{
    public string Title { get; set; }
}

控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.Title = "ViewBag title";
        ViewData["Title"] = "ViewData title";
        var model = new MyViewModel
        {
            Title = "Model title"
        };
        return View(model);
    }
}

查看:

@model AppName.Models.MyViewModel
@{
    ViewBag.Title = "Home Page";
}

@Html.EditorFor(x => x.Title)

@{
    ViewBag.Title = "Some other title";
}

因此,无论我们如何尝试在此处滥用,编辑器模板都会使用正确的模型标题(如果我们使用 Html.Editor("Title"),则情况并非如此)。

【讨论】:

  • 谢谢,但我明白这一点。我需要让 View 女巫不知道具体的模型类型。因此它使用反射来获取属性名称并创建编辑器。你可以给我建议如何在没有 Html.Editor(string) 方法的情况下从元数据生成表单。或者如何避免绑定到 ViewBag 属性。
  • @gimalay,哦,我现在明白了。你可以看看following blog post,它解释了编辑器模板是如何实现的。 Object.ascx 编辑器模板展示了如何在不知道模型类型的情况下构建动态表单。
  • 此示例包含与我完全相同的代码。只是 Html.Editor(string) 调用。
  • 谢谢,我找到了其他方法。
  • @gimalay:请分享另一种方法,因为我现在确实将问题缩小到我的 EditorTemplates/Object.cshtml (也基于上面提到的 Brad Wilson 代码)。 @Html.Editor(prop.PropertyName) 行绑定到错误的标题,但我无法修复它。当我将其更改为 Html.EditorFor 时,它不会绑定到正确的 EditorTemplate 和值。
【解决方案2】:

正如其他答案所建议的那样,使用 EditorFor 而不是 Editor 似乎可以解决这个问题。但是,使用EditorFor 需要在编译时了解模型类型和属性类型,而Object.cshtml 则不然。

您仍然可以通过使用反射构建和调用正确的通用构造的EditorFor 方法来做到这一点。这样做的代码真的很乱,所以这里有一些可重用的扩展方法来为你做。

Object.cshtml 中像这样使用它们,其中propModelMetadata 的一个实例,就像问题中一样:

@Html.DisplayFor(prop)
@Html.LabelFor(prop)
@Html.EditorFor(prop)
@Html.ValidationMessageFor(prop)

以下是扩展方法:

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace ASP
{
    public static class NonStronglyTypedStronglyTypedHtmlHelpers
    {
        public static MvcHtmlString DisplayFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.DisplayFor, prop);
        }

        public static MvcHtmlString EditorFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.EditorFor, prop);
        }

        public static MvcHtmlString LabelFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.LabelFor, prop);
        }

        public static MvcHtmlString ValidationMessageFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.ValidationMessageFor, prop);
        }

        private static MvcHtmlString StronglyTypedHelper(HtmlHelper html, Func<HtmlHelper<object>, GenericHelper<object>> accessMethod, ModelMetadata prop)
        {
            var constructedMethod = MakeStronglyTypedHelper(html, accessMethod, prop);
            var genericPropertyExpression = MakePropertyExpression(prop);
            var typedHtmlHelper = MakeStronglyTypedHtmlHelper(html, prop.ContainerType);

            return (MvcHtmlString)constructedMethod.Invoke(null, new object[] { typedHtmlHelper, genericPropertyExpression });
        }

        private static MethodInfo MakeStronglyTypedHelper(HtmlHelper html, Func<HtmlHelper<object>, GenericHelper<object>> accessMethod, ModelMetadata prop)
        {
            var objectTypeHelper = new HtmlHelper<object>(html.ViewContext, html.ViewDataContainer, html.RouteCollection);
            var runMethod = accessMethod(objectTypeHelper);
            var constructedMehtod = runMethod.Method;
            var genericHelperDefinition = constructedMehtod.GetGenericMethodDefinition();
            return genericHelperDefinition.MakeGenericMethod(prop.ContainerType, prop.ModelType);
        }

        private static object MakeStronglyTypedHtmlHelper(HtmlHelper html, Type type)
        {
            var genericTypeDefinition = typeof(HtmlHelper<>);
            var constructedType = genericTypeDefinition.MakeGenericType(type);
            var constructor = constructedType.GetConstructor(new[] { typeof(ViewContext), typeof(IViewDataContainer), typeof(RouteCollection) });
            return constructor.Invoke(new object[] { html.ViewContext, html.ViewDataContainer, html.RouteCollection });
        }

        private static LambdaExpression MakePropertyExpression(ModelMetadata prop)
        {
            var propertyInfo = prop.ContainerType.GetProperty(prop.PropertyName);
            var expressionParameter = Expression.Parameter(prop.ContainerType);
            var propertyExpression = Expression.MakeMemberAccess(expressionParameter, propertyInfo);
            return Expression.Lambda(propertyExpression, expressionParameter);
        }

        private delegate MvcHtmlString GenericHelper<TModel>(Expression<Func<TModel, object>> expression);
    }
}

【讨论】:

  • 在使用复杂数据结构时在MakeStronglyTypedHtmlHelper 方法的最后一行抛出异常:@Html.DisplayFor(x =&gt; x.Address.Street) 知道如何解决它吗?
  • @Dmitry,不确定抱歉。我什至不记得代码是如何工作的! :)
【解决方案3】:

我自己找到了部分解决方案。

只需使用:

    @Html.EditorForModel()

代替:

    @foreach (var property in Model.GetMetadata().Properties)
    {
            <div class="editor-label">
                @Html.Label(property.PropertyName)
            </div>
            <div class="editor-field">
                @Html.Editor(property.PropertyName) 
                @Html.ValidationMessage(property.PropertyName)
            </div>

    }

Html.EditorForModel() 方法返回相同的结果,但没有描述的问题。

【讨论】:

  • 您介意在应用此解决方法后显示您的 EditorTemplates/Object.cshtml 的样子吗?当我在我的 Object.cshtml 模板中尝试 Html.EditorForModel() 时,它似乎忽略了所有属性并且不呈现任何内容。谢谢。
  • 我没有以任何方式更改对象模板。
  • 如果您希望 EditorForModel 对您的对象图进行深度遍历,您需要编辑 MVC 源代码。默认情况下它只会深入 2 层(这意味着只有视图模型上的原生属性)。
  • 我认为EditorForModel 使用Object.cshtml,所以这应该会导致递归逻辑,我认为框架会采用这种逻辑,从而导致没有渲染任何内容。因此,Editor 的使用被删除,因此原始问题也被删除,但这用新问题替换了该问题:Object.cshtml 不再起作用。
【解决方案4】:

我解决了同样的问题。改用此语法 Html.Editor

@(Html.EditorFor(p => property.Model))

【讨论】:

  • 这个方法一开始看起来是个好主意,但是试了之后发现有点问题。它向框架注册了错误的属性名称(可能还有类型)。这似乎把各种事情搞砸了,包括生成的 HTML、模型绑定、验证,可能还有其他。
猜你喜欢
  • 2020-07-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-20
  • 2013-03-22
  • 2011-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多