18

MVC 3 RTM にアップグレードした後、以前は機能していた例外が発生します。

これがシナリオです。同じ基になるインターフェイス IActivity と IOwned を使用するオブジェクトがいくつかあります。

IActivity implements IOwned (another interface)

public interface IActivity:IOwned {...}

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

他の具象パーシャルから再利用するために IActivity を使用する部分ビューがあります。

これがActivity Partialの定義です。

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

ただし、例外がスローされます。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
4

6 に答える 6

15

MVC チームから:

残念ながら、コードは実際には修正されたバグを悪用していました。このバグは、ModelMetadata 用の式のコンテナーが、包含型ではなく宣言型に誤って設定されていました。仮想プロパティと検証/モデル メタデータが必要なため、このバグを修正する必要がありました。

インターフェイスベースのモデルを使用することはお勧めしません (また、バグ修正によって課せられた制限を考慮すると、現実的にサポートすることもできません)。抽象基本クラスに切り替えると、問題が解決します。

于 2011-03-30T19:53:04.737 に答える
14

System.Web.Mvc.ModelMetadata. FromLambdaExpressionASP.NET MVC 3 には、取得している例外を説明するメソッドに重大な変更/バグがあります。

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、コントロールの型として指定することができます (どちらもうまくいかないことはわかっています)。

于 2011-01-15T23:28:32.750 に答える
7

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 &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.asaxApplication_Startに設定します

ModelMetadataProviders.Current = new MyMetaDataProvider();
于 2011-10-19T19:21:56.910 に答える
6

あなたが興味を持っているなら、私は私のアプリでそれを回避するための小さな回避策を実装しました. 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;
        }              
    }
}

次に、global.asax Application_Start() で MetaDataProvider の実装を変更するだけです。

ModelMetadataProviders.Current = new MyMetaDataProvider();

これはこれまでで最高のコードではありませんが、仕事はします。

于 2011-09-06T18:21:19.117 に答える
5

タイプキャストを使ってみてください。私のプロジェクトでは機能しますが、resharper は冗長として強調表示します。

あなたのコードの場合、解決策は次のようになります

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IActivity>" %>
<%: Html.HiddenFor(item => ((IOwned)item).AuthorId) %>
于 2011-03-21T07:56:51.720 に答える
0

Anthony Johnston の回答をさらに進めると、 AssociatedValidatorProvider.GetValidatorsForProperty()メソッドは、継承インターフェイスを基本インターフェイスではなくコンテナー タイプとして使用しようとするため、DataAnnotations を使用すると例外が発生する場合があるため、再度プロパティを見つけることができません。

これは、GetValidatorsForProperty メソッドから反映されたコードです (propertyDescriptor 変数が null になり、例外がスローされる原因となる 2 行目です)。

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);
    }
}
于 2013-04-04T09:02:53.770 に答える