ASP.NET MVC 3 HtmlHelper Exception does not recognize ModelMetadata on inherited interface ASP.NET MVC 3 HtmlHelper Exception does not recognize ModelMetadata on inherited interface asp.net asp.net

ASP.NET MVC 3 HtmlHelper Exception does not recognize ModelMetadata on inherited interface


From the MVC team:

Unfortunately, the code was actually exploiting a bug that was fixed, where the container of an expression for ModelMetadata purposes was inadvertently set to the declaring type instead of the containing type. This bug had to be fixed because of the needs of virtual properties and validation/model metadata.

Having interface-based models is not something we encourage (nor, given the limitations imposed by the bug fix, can realistically support). Switching to abstract base classes would fix the issue.


There has been a breaking change/bug in ASP.NET MVC 3 in the System.Web.Mvc.ModelMetadata. FromLambdaExpression method which explains the exception you are getting:

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;}...

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;}...

Notice how the containerType variable is assigned a different value. So in your case in ASP.NET MVC 2.0 it was assigned the value of IOwned which is the correct declaring type of the AuthorId property whereas in ASP.NET MVC 3.0 it is assigned to IActivity and later when the framework tries to find the property it crashes.

That's the cause. As far as the resolution is concerned I would wait for some official statement from Microsoft. I can't find any relevant information about this in the Release Notes document. Is it a bug or some feature which needs to be workarounded here?

For now you could either use the non-strongly typed Html.Hidden("AuthorId") helper or specify IOwned as type for your control (I know both suck).


With thanks to Burcephal, whos answer pointed me in the right direction

You can create a MetaDataProvider which will get around this issue, the code here adds to the code in the base class checking for the property on implemented interfaces of a model which is itself an interface.

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 &apos;{0}&apos; 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);    }}

and as above, set your provider the global.asax Application_Start

ModelMetadataProviders.Current = new MyMetaDataProvider();