Dax*_*x70 18 asp.net html-helper modelmetadata asp.net-mvc-3
升级到MVC 3 RTM后,我得到了一个以前工作的例外.
这是场景.我有几个使用相同底层接口IActivity和IOwned的对象.
IActivity implements IOwned (another interface)
public interface IActivity:IOwned {...}
public interface IOwned
{
int? AuthorId {get;set;}
}
Run Code Online (Sandbox Code Playgroud)
我有一个局部视图,它使用IActivity从其他具体的部分重用.
以下是Activity Partial的定义.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IActivity>" %>
<%: Html.HiddenFor(item => item.AuthorId) %>
Run Code Online (Sandbox Code Playgroud)
但是,它会引发异常.它在ModelMetadata中找不到AuthorId.
我猜测在之前的版本中它查看了IActivity实现的接口.
任何想法,建议,到处都没有重复类似的接口?
复制下面的堆栈跟踪.
[ArgumentException: The property IActivity.AuthorId could not be found.]
System.Web.Mvc.AssociatedMetadataProvider.GetMetadataForProperty(Func`1 modelAccessor, Type containerType, String propertyName) +498313
System.Web.Mvc.ModelMetadata.GetMetadataFromProvider(Func`1 modelAccessor, Type modelType, String propertyName, Type containerType) +101
System.Web.Mvc.ModelMetadata.FromLambdaExpression(Expression`1 expression, ViewDataDictionary`1 viewData) +393
System.Web.Mvc.Html.InputExtensions.HiddenFor(HtmlHelper`1 htmlHelper, Expression`1 expression, IDictionary`2 htmlAttributes) +57
System.Web.Mvc.Html.InputExtensions.HiddenFor(HtmlHelper`1 htmlHelper, Expression`1 expression) +51
ASP.views_shared_activity_ascx.__Render__control1(HtmlTextWriter __w, Control parameterContainer) in c:\Users\...\Documents\Visual Studio 2010\Projects\ngen\trunk\...\Views\Shared\Activity.ascx:3
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +109
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Control.Render(HtmlTextWriter writer) +10
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +208
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Page.Render(HtmlTextWriter writer) +29
System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer) +43
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3060
Run Code Online (Sandbox Code Playgroud)
Dar*_*rov 15
ASP.NET MVC 3中的一个重大变化/错误在该System.Web.Mvc.ModelMetadata. FromLambdaExpression方法中解释了您获得的异常:
ASP.NET MVC 2.0:
...
case ExpressionType.MemberAccess:
{
MemberExpression body = (MemberExpression) expression.Body;
propertyName = (body.Member is PropertyInfo) ? body.Member.Name : null;
containerType = body.Member.DeclaringType;
flag = true;
break;
}
...
Run Code Online (Sandbox Code Playgroud)
ASP.NET MVC 3.0
...
case ExpressionType.MemberAccess:
{
MemberExpression body = (MemberExpression) expression.Body;
propertyName = (body.Member is PropertyInfo) ? body.Member.Name : null;
containerType = body.Expression.Type;
flag = true;
break;
}
...
Run Code Online (Sandbox Code Playgroud)
注意如何为containerType变量分配不同的值.所以在你的情况下,在ASP.NET MVC 2.0中,它的值被赋值为属性IOwned的正确声明类型,AuthorId而在ASP.NET MVC 3.0中,它被分配给IActivity以后,当框架试图找到它崩溃的属性时.
这就是原因.就决议而言,我会等待微软的一些官方声明.我在发行说明文档中找不到任何相关信息.这是一个需要在这里工作的bug或某些功能吗?
现在你可以使用非强类型的Html.Hidden("AuthorId")帮助器或指定IOwned控件的类型(我知道两个都很糟糕).
Sco*_*man 14
来自MVC团队:
不幸的是,代码实际上正在利用已修复的错误,其中用于ModelMetadata目的的表达式容器无意中设置为声明类型而不是包含类型.由于虚拟属性和验证/模型元数据的需要,必须修复此错误.
拥有基于接口的模型不是我们鼓励的(也不是,考虑到错误修复所施加的限制,可以实际支持).切换到抽象基类可以解决问题.
感谢Burcephal,回答的人指出了正确的方向
你可以创建一个解决这个问题的MetaDataProvider,这里的代码添加到基类中的代码中,检查模型的实现接口上的属性,该模型本身就是一个接口.
public class MyMetadataProvider
: EmptyModelMetadataProvider {
public override ModelMetadata GetMetadataForProperty(
Func<object> modelAccessor, Type containerType, string propertyName) {
if (containerType == null) {
throw new ArgumentNullException("containerType");
}
if (String.IsNullOrEmpty(propertyName)) {
throw new ArgumentException(
"The property '{0}' cannot be null or empty", "propertyName");
}
var property = GetTypeDescriptor(containerType)
.GetProperties().Find(propertyName, true);
if (property == null
&& containerType.IsInterface) {
property = (from t in containerType.GetInterfaces()
let p = GetTypeDescriptor(t).GetProperties()
.Find(propertyName, true)
where p != null
select p
).FirstOrDefault();
}
if (property == null) {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
"The property {0}.{1} could not be found",
containerType.FullName, propertyName));
}
return GetMetadataForProperty(modelAccessor, containerType, property);
}
}
Run Code Online (Sandbox Code Playgroud)
如上所述,将您的提供程序设置为global.asax Application_Start
ModelMetadataProviders.Current = new MyMetaDataProvider();
Run Code Online (Sandbox Code Playgroud)
如果您有兴趣,我已经在我的应用程序中实现了一些工作.当我浏览MVC源代码时,我发现下面命名的FromLambdaExpression方法将调用MetaDataProvider,它是一个可覆盖的单例.所以我们可以实现那个实际尝试固有接口的类,如果第一个不起作用的话.它也将上升到接口树.
public class MyMetaDataProvider : EmptyModelMetadataProvider
{
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
try
{
return base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
}
catch(Exception ex)
{
//Try to go up to type tree
var types = containerType.GetInterfaces();
foreach (var container in types)
{
if (container.GetProperty(propertyName) != null)
{
try
{
return GetMetadataForProperty(modelAccessor, container, propertyName);
}
catch
{
//This interface did not work
}
}
}
//If nothing works, then throw the exception
throw ex;
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后,只需在global.asax Application_Start()中实现MetaDataProvider的实现
ModelMetadataProviders.Current = new MyMetaDataProvider();
Run Code Online (Sandbox Code Playgroud)
它不是有史以来最好的代码,但它完成了这项工作.
尝试使用类型转换。它适用于我的项目,尽管 resharper 强调它是多余的。
对于您的代码,解决方案是
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IActivity>" %>
<%: Html.HiddenFor(item => ((IOwned)item).AuthorId) %>
Run Code Online (Sandbox Code Playgroud)