ASP.NET исключение MVC 3 HtmlHelper не распознает ModelMetadata на унаследованном интерфейсе

после обновления до MVC 3 RTM я получаю исключение, где он ранее работал.

вот сценарий. У меня есть несколько объектов, которые используют одни и те же базовые интерфейсы IActivity и IOwned.

IActivity implements IOwned (another interface)

public interface IActivity:IOwned {...}

public interface IOwned 
{
    int? AuthorId {get;set;}
}

у меня есть частичное представление, которое использует IActivity для повторного использования из других конкретных частичных элементов.

вот определение частичной активности.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IActivity>" %>
<%: Html.HiddenFor(item => item.AuthorId) %>

однако он создает исключение. Не можете найти номер в ModelMetadata.

Я предполагаю, что в предыдущей версии он смотрел на реализованные интерфейсы 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...DocumentsVisual Studio 2010Projectsngentrunk...ViewsSharedActivity.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

6 ответов


там было нарушение изменения / ошибка в 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;
}
...

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

обратите внимание, как containerType переменной присваивается другое значение. Так что в вашем случае ASP.NET MVC 2.0 было присвоено значение IOwned, который является правильным объявления типа AuthorId свойство тогда как в ASP.NET MVC 3.0 присваивается IActivity и позже, когда платформа пытается найти свойство, оно аварийно завершает работу.

вот причина. Что касается резолюции, я бы подождал официального заявления от Microsoft. Я не могу найти никакой соответствующей информации об этом в документе примечаний к выпуску. Это ошибка или что-то характеристика, который должен быть workarounded здесь?

теперь можно использовать не типизированы Html.Hidden("AuthorId") helper или укажите IOwned как тип для вашего управления (я знаю оба отстой).


от команды MVC:

к сожалению, код был на самом деле использование исправленной ошибки, где контейнер выражения для Цели ModelMetadata были непреднамеренно установлено объявление введите вместо содержащего типа. Эта ошибка должна быть исправлена из-за потребности виртуальных свойств и проверка / метаданные модели.

наличие интерфейсных моделей не является что-то мы призываем (не учитывая ограничения наложено исправлением ошибки, может реально поддержать). Переключение чтобы абстрактные базовые классы исправляли вопрос.


благодаря Бурцефалу, чей ответ указал мне в правильном направлении

Вы можете создать 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 &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);
    }
}

и, как указано выше, установите поставщика глобальным.событий Application_Start эйсакс

ModelMetadataProviders.Current = new MyMetaDataProvider();

Если вы заинтересованы, я реализовал небольшую работу для него в своем приложении. Когда я просматривал исходный код MVC, я обнаружил, что метод FromLambdaExpression, названный ниже, будет вызывать MetaDataProvider, который является переопределяемым синглтоном. Поэтому мы могли бы просто реализовать этот класс, который фактически попробует унаследованные intefaces, если первый не работает. Он также поднимется на дерево intefaces.

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

а затем просто cange реализации MetaDataProvider в мировой.эйсакс событий Application_Start()

ModelMetadataProviders.Current = new MyMetaDataProvider();

это не лучший код, но он выполняет свою работу.


попробуйте использовать typecast. Он работает для моего проекта, хотя resharper подчеркивает его как избыточный.

для вашего кода решение будет

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IActivity>" %>
<%: Html.HiddenFor(item => ((IOwned)item).AuthorId) %>

достижения Энтони Джонстона ответ, вы можете обнаружить, что получаете исключения при использовании DataAnnotations, как AssociatedValidatorProvider.GetValidatorsForProperty () метод попытается использовать наследующий интерфейс в качестве типа контейнера, а не базового, и поэтому не сможет найти свойство снова.

это отраженный код из метода GetValidatorsForProperty (это вторая строка, которая вызывает propertyDescriptor переменная должна быть null и, следовательно, исключение, которое должно быть брошено):

private IEnumerable<ModelValidator> GetValidatorsForProperty(ModelMetadata metadata, ControllerContext context)
{
    ICustomTypeDescriptor typeDescriptor = this.GetTypeDescriptor(metadata.ContainerType);
    PropertyDescriptor propertyDescriptor = typeDescriptor.GetProperties().Find(metadata.PropertyName, true);
    if (propertyDescriptor != null)
    {
        return this.GetValidators(metadata, context, propertyDescriptor.Attributes.OfType<Attribute>());
    }
    else
    {
        object[] fullName = new object[] { metadata.ContainerType.FullName, metadata.PropertyName };
        throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyNotFound, fullName), "metadata");
    }
}

Если это так, я считаю, что следующий код может помочь, поскольку он гарантирует, что ContainerType установлен в тип, на котором находится свойство, а не Тип модели представления.

отказ от ответственности: кажется, работает нормально, но я еще не полностью протестировал его, поэтому он может иметь нежелательные эффекты! Я также понимаю, что он написан не идеально, но я пытался сохранить формат, аналогичный предыдущему ответу для простота сравнения. :)

public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
    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 containerTypeToUse = containerType;

        var property = GetTypeDescriptor(containerType)
            .GetProperties().Find(propertyName, true);
        if (property == null
            && containerType.IsInterface)
        {

            var foundProperty = (from t in containerType.GetInterfaces()
                        let p = GetTypeDescriptor(t).GetProperties()
                            .Find(propertyName, true)
                        where p != null
                        select (new Tuple<System.ComponentModel.PropertyDescriptor, Type>(p, t))
                        ).FirstOrDefault();

            if (foundProperty != null)
            {
                property = foundProperty.Item1;
                containerTypeToUse = foundProperty.Item2;
            }
        }


        if (property == null)
        {
            throw new ArgumentException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    "The property {0}.{1} could not be found",
                    containerType.FullName, propertyName));
        }

        return GetMetadataForProperty(modelAccessor, containerTypeToUse, property);
    }
}