问题
My Model contains a property named Title
, and in my Create
view I set the page title using ViewBag.Title
.
This creates the following problem: the form generated by Html.Editor
will display the text from ViewBag.Title
, instead of the model's Title
value.
The only workaround I have found is first calling Html.Editor
, and then setting the View.Title
.
Does anyone have a better solution?
Edit 1: I am using MVC 3.
Edit 2: This is my 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")
}
}
回答1:
I would recommend using EditorFor
instead of Editor
.
Html.EditorFor(x => x.Title)
instead of:
Html.Editor("Title")
This way not only that the view takes advantage of your view model but it behaves as expected in this case.
Example with ASP.NET MVC 3.0 RTM (Razor):
Model:
public class MyViewModel
{
public string Title { get; set; }
}
Controller:
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);
}
}
View:
@model AppName.Models.MyViewModel
@{
ViewBag.Title = "Home Page";
}
@Html.EditorFor(x => x.Title)
@{
ViewBag.Title = "Some other title";
}
So no matter how much we try to abuse here the editor template uses the correct model title (which is not the case if we used Html.Editor("Title")
).
回答2:
As suggested by the other answers, using EditorFor
instead of Editor
seems to work around the problem. However, using EditorFor
requires knowledge of the model type and property type at compile-time, which isn't the case for Object.cshtml
.
You can still do this by building up and calling the correct generically-constructed EditorFor
method using reflection. The code to do this is really messy, so here are some re-usable extension methods to do it for you.
Use them like this in Object.cshtml
where prop
is an instance of ModelMetadata
like in the question:
@Html.DisplayFor(prop)
@Html.LabelFor(prop)
@Html.EditorFor(prop)
@Html.ValidationMessageFor(prop)
Here are the extension methods:
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);
}
}
回答3:
I found partial solution myself.
Just use:
@Html.EditorForModel()
instead of:
@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() method return same results, but without described problem.
回答4:
I solve same problem. Use this syntax instead Html.Editor
@(Html.EditorFor(p => property.Model))
来源:https://stackoverflow.com/questions/4375323/binding-conflict-between-a-property-named-title-in-my-model-and-view-title-in-my