Потому что я столкнулся с проблемой и подумал, что полезно поделиться потенциальным решением.

обновления до 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) %>

Однако это исключение. Он не может найти 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\...\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
 Russ Cam15 янв. 2011 г., 23:39
Вы пытались пройти через код структуры?
 Dax7015 янв. 2011 г., 23:59
Нет, я не прошел через код Framework. Я надеялся, что нет;) Думаю, сейчас я могу обойти это, переключившись на <%: Html.Hidden ("AuthorId")%> Однако это выглядит как критическое изменение в FX IMHO.

Ответы на вопрос(6)

хотя Resharper выделяет его как избыточный.

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

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

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

и, как указано выше, установите у вашего провайдера global.asax Application_Start

ModelMetadataProviders.Current = new MyMetaDataProvider();
 Phil Cooper08 сент. 2016 г., 09:12
@DuncanMack да, это возможно. Хороший ответ и комментарии, хотя я реализовал этот обходной путь, и он работает нормально.
 Phil Cooper23 авг. 2016 г., 11:08
@DuncanMack не могли бы вы уточнить свой комментарий? Я попытался пропатчить MetaDataProvider, чтобы обойти проблему интерфейса, и он, кажется, работает сDisplayAttribute (хотя я не жалуюсь!).
 DuncanMack16 июн. 2015 г., 00:34
Я знаю, что это старый ответ, но сначала мы нашли его полезным. К сожалению, мы обнаружили, что наши предупреждения валидации утратили «дружественное» имя, предоставляемое с помощью DisplayAttribute. Поэтому вместо того, чтобы видеть «Поле Местоположение обязательно», они могут видеть что-то вроде «Поле MyPropertyName обязательно». Не уверен, что обходной путь, поэтому мы будем использовать абстрактные классы, а не интерфейсы. Просто головы до будущих посетителей.
 jessegavin12 дек. 2011 г., 17:54
Это решило проблему в нашем приложении. Если мы столкнемся с какими-либо плохими побочными эффектами, я обязательно вернусь сюда, чтобы прокомментировать. Большое спасибо!
 DuncanMack07 сент. 2016 г., 22:26
@PhilCooper Мне нужно вернуться и настроить тестовый проект, чтобы вернуться к этому. Возможно, это было учтено в последующих обновлениях (либо специально для MVC, либо для более широкой среды выполнения). Не уверен, что доберусь до этого, но сообщу, если сделаю.
Решение Вопроса

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. Я не могу найти соответствующую информацию об этом в документе Примечания к выпуску. Это ошибка или какой-тохарактерная черта что нужно обойти здесь?

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

 Dax7016 янв. 2011 г., 22:19
Круто, спасибо!
 Jordan23 февр. 2011 г., 17:37
Вау! Это изменение не помогло. Это мешает нам перейти на MVC 3. Надеюсь, это скоро исправят!
 Darin Dimitrov16 янв. 2011 г., 22:16
@ Dax70, я отправил билет. Вы можете следить за этим здесь:connect.microsoft.com/VisualStudio/feedback/details/636341/... Я надеюсь, что мы получим какое-то официальное заявление или обходной путь.
 Dax7016 янв. 2011 г., 22:15
Я согласен. Да, я вроде пришел к тому же выводу об использовании не-лямбда-помощников. И спасибо, что сделали различий в источнике. Еще раз спасибо, очень полезно.

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

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

 Dax7022 апр. 2011 г., 05:05
Причиной для интерфейсов был EF. Помимо немедленного обновления до EF 4.1 (Code First) Magic Unicorns;). EF принимает базовый класс с EntityObject. И переопределение генерации кода EF, даже если он с T4 просто кажется большой работой, интерфейсы казались прагматическим подходом к полиморфизму. Обходной путь в порядке, особенно если он предотвращает другие проблемы.
 Jab31 мар. 2011 г., 16:55
Спасибо за ответ на вопрос «почему». Я вижу это изменение, кусающее довольно много людей. Я не могу придумать ни одной другой ситуации, когда передается лямбда, и это приводит к исключению свойства, не найденному из-за наследования интерфейсов.
 Scott Hanselman01 апр. 2011 г., 10:57
Если мы сможем доказать, что это имеет большое значение и затрагивает многих людей, мы можем обосновать, чтобы это исправить / изменить.
 David Alpert27 апр. 2011 г., 21:59
Я не впечатлен тем, что «Наличие основанных на интерфейсе моделей - это не то, что мы поощряем». Должен ли я бросить я из твердого тела в окно?
 Teevus02 авг. 2011 г., 08:17
Я согласен с @DavidAlpert. Я склонен использовать много частичных представлений, которые используют интерфейсы для своих моделей представлений. Причина в том, что это облегчает повторное использование представлений, где вы не смогли бы сделать это без большого соответствия между моделями представлений.

я реализовал небольшую работу для этого в моем приложении. Когда я просматривал исходный код 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 = containerTy,pe.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;
        }              
    }
}

а затем просто поменять реализацию MetaDataProvider в global.asax Application_Start ()

ModelMetadataProviders.Current = new MyMetaDataProvider();

Это не лучший код когда-либо, но он делает свою работу.

Энтони Джонстон ответ, вы можете обнаружить, что вы получаете исключения при использовании 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);
    }
}
 Steven Brownett04 апр. 2013 г., 16:45
Потому что я столкнулся с проблемой и подумал, что полезно поделиться потенциальным решением.

Ваш ответ на вопрос