Razorの動的匿名型が原因でRuntimeBinderExceptionが発生する


156

次のエラーが発生します。

「オブジェクト」には「RatingName」の定義が含まれていません

匿名の動的タイプを見ると、RatingNameがあることは明らかです。

エラーのスクリーンショット

タプルを使用してこれを実行できることはわかっていますが、エラーメッセージが発生する理由を知りたいのですが。

回答:


240

私の意見では、内部プロパティを持つ匿名型は、.NETフレームワークの設計決定には適していません。

これは、この問題を解決するための迅速で優れた拡張機能です。つまり、匿名オブジェクトをすぐにExpandoObjectに変換します。

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> anonymousDictionary =  new RouteValueDictionary(anonymousObject);
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var item in anonymousDictionary)
        expando.Add(item);
    return (ExpandoObject)expando;
}

使い方はとても簡単です。

return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());

もちろんあなたの見解では:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}

2
+1私は特にHtmlHelper.AnonymousObjectToHtmlAttributesを探していましたが、これはすでに完全に組み込まれている必要があり、同様のハンドロールされたコードでホイールを再発明したくありませんでした。
Chris Marisic、

3
単純に厳密に型指定されたバッキングモデルを作成する場合と比較して、このパフォーマンスはどの程度ですか?
GONeale

@ DotNetWise、IDictionary <string、object> anonymousDictionary = new RouteDictionary(object)しか実行できないのに、なぜHtmlHelper.AnonymousObjectToHtmlAttributesを使用するのですか?
ジェレミー・ボイド

HtmlHelper.AnonymousObjectToHtmlAttributesをテストしましたが、期待どおりに動作します。ソリューションも機能します。どちらか簡単に思える方法を使用してください:)
Adaptabi '25

永続的なソリューションにしたい場合は、コントローラーの動作をオーバーライドすることもできますが、匿名型を識別したり、自分で型から文字列/オブジェクトディクショナリを作成したりするなど、さらにいくつかの回避策が必要です。それを行う場合は、次のようにオーバーライドできます。protected override System.Web.Mvc.ViewResult View(string viewName、string masterName、object model)
Johny Skovdal

50

私は関連する質問で答えを見つけました。回答は、David Ebboのブログ投稿「MVCビューに匿名オブジェクトを渡し、動的オブジェクトを使用してそれらにアクセスする」で指定されています

これは、匿名型が内部でコントローラーに渡されるため、宣言されているアセンブリ内からのみアクセスできるためです。ビューは個別にコンパイルされるため、動的バインダーはそのアセンブリー境界を超えることができないと不平を言います。

しかし、考えてみると、動的バインダーからのこの制限は実際にはかなり人為的なものです。プライベートリフレクションを使用しても、内部メンバーへのアクセスを妨げるものは何もありません(そう、中程度の信頼でも機能します)。そのため、デフォルトの動的バインダーは、CLRランタイムで許可されていることを実行させる代わりに、C#コンパイルルール(内部メンバーにアクセスできない場合)を強制する方法から外れています。


それを私にビート:)私はかみそりエンジン(上の1への前駆体と、この問題に遭遇したrazorengine.codeplex.com
Buildstarted

これは実際の回答ではなく、「受け入れられた回答」についての詳細な説明ではありません。
Adaptabi

4
@DotNetWise:エラーが発生する理由を説明しますが、これは問題でした。また、素敵な回避策を提供するための私のupvoteを取得します:)
ルーカス

参考までに:この回答は非常に古くなっています-参照されたブログ投稿の冒頭で著者が赤字で言っているように
Simon_Weaver

@Simon_Weaverしかし、投稿の更新では、MVC3 +でどのように機能するかについては説明されていません。-MVC 4で同じ問題に遭遇しました。動的を使用する現在「祝福された」方法のポインタはありますか?
Cristian Diaconescu 2013

24

ToExpandoの使用メソッドするのが最善のソリューションです。

System.Webアセンブリを必要しないバージョンは次のとおりです。

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(anonymousObject))
    {
        var obj = propertyDescriptor.GetValue(anonymousObject);
        expando.Add(propertyDescriptor.Name, obj);
    }

    return (ExpandoObject)expando;
}

1
それはより良い答えです。HtmlHelperが代替回答でアンダースコアを使用して何を行うかはわかりません。
デン

+1は一般的な回答です。これはASP / MVC以外でも役立ちます
codenheim

ネストされた動的プロパティはどうですか?それらは引き続き動的です...例: `{foo:" foo "、nestedDynamic:{blah:" blah "}}
スポーツ

16

匿名型からモデルを作成してから、匿名オブジェクトをExpandoObjectこのようなものに変換しようとする代わりに...

var model = new 
{
    Profile = profile,
    Foo = foo
};

return View(model.ToExpando());  // not a framework method (see other answers)

ExpandoObject直接作成することができます:

dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;

return View(model);

次に、ビューでモデルタイプを動的に設定し@model dynamic、プロパティに直接アクセスできます。

@Model.Profile.Name
@Model.Foo

私は通常、ほとんどのビューに強く型付けされたビューモデルをお勧めしますが、この柔軟性は便利な場合があります。


@yohal確かにできます-それは個人的な好みだと思います。一般にページモデルに関係のないその他のページデータにはViewBagを使用することを好みます。テンプレートに関連している可能性があり、Modelをプライマリモデルとして保持します
Simon_Weaver

2
ところで、これはデフォルトなので、@ model dynamicを追加する必要はありません
yoel halb

anon objsをexpandoオブジェクトに変換するメソッドを実装するのに必要な時間は非常に時間がかかりました……おかげでヒープ
h-rai

5

フレームワーク即興インターフェースを使用できますを、匿名タイプをでラップできます。

あなたはただ返すだけIEnumerable<IMadeUpInterface>で、Linqの最後に.AllActLike<IMadeUpInterface>();これは機能します。これは、匿名型を宣言したアセンブリのコンテキストでDLRを使用して匿名プロパティを呼び出すためです。


1
恐ろしい小さなトリック:)ただし、少なくともこの場合は、一連のパブリックプロパティを持つ単純なクラスよりも優れているかどうかはわかりません。
Andrew Backer '19

4

コンソールアプリケーションを作成し、Mono.Cecilを参照として追加(NuGetから追加できます)してから、コードを記述します。

static void Main(string[] args)
{
    var asmFile = args[0];
    Console.WriteLine("Making anonymous types public for '{0}'.", asmFile);

    var asmDef = AssemblyDefinition.ReadAssembly(asmFile, new ReaderParameters
    {
        ReadSymbols = true
    });

    var anonymousTypes = asmDef.Modules
        .SelectMany(m => m.Types)
        .Where(t => t.Name.Contains("<>f__AnonymousType"));

    foreach (var type in anonymousTypes)
    {
        type.IsPublic = true;
    }

    asmDef.Write(asmFile, new WriterParameters
    {
        WriteSymbols = true
    });
}

上記のコードは、入力引数からアセンブリファイルを取得し、Mono.Cecilを使用してアクセス可能性を内部からパブリックに変更し、それによって問題が解決されます。

このプログラムは、WebサイトのPost Buildイベントで実行できます。私はこれについて中国語でブログ投稿しましたが、コードとスナップショットを読むだけでいいと思います。:)


2

受け入れられた回答に基づいて、私はコントローラでオーバーライドして、それを一般的かつ舞台裏で機能させるようにしました。

これがコードです:

protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
    base.OnResultExecuting(filterContext);

    //This is needed to allow the anonymous type as they are intenal to the assembly, while razor compiles .cshtml files into a seperate assembly
    if (ViewData != null && ViewData.Model != null && ViewData.Model.GetType().IsNotPublic)
    {
       try
       {
          IDictionary<string, object> expando = new ExpandoObject();
          (new RouteValueDictionary(ViewData.Model)).ToList().ForEach(item => expando.Add(item));
          ViewData.Model = expando;
       }
       catch
       {
           throw new Exception("The model provided is not 'public' and therefore not avaialable to the view, and there was no way of handing it over");
       }
    }
}

これで、匿名オブジェクトをモデルとして渡すだけで、期待どおりに動作します。



0

RuntimeBinderExceptionがトリガーされた理由は、他の投稿で良い答えがあると思います。実際に機能させる方法を説明することに集中します。

回答を参照して、ASP.NET MVCの匿名タイプコレクションで @DotNetWiseおよびBindingビューを参照してください。

まず、拡張用の静的クラスを作成します

public static class impFunctions
{
    //converting the anonymous object into an ExpandoObject
    public static ExpandoObject ToExpando(this object anonymousObject)
    {
        //IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
        IDictionary<string, object> anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject);
        IDictionary<string, object> expando = new ExpandoObject();
        foreach (var item in anonymousDictionary)
            expando.Add(item);
        return (ExpandoObject)expando;
    }
}

コントローラ内

    public ActionResult VisitCount()
    {
        dynamic Visitor = db.Visitors
                        .GroupBy(p => p.NRIC)
                        .Select(g => new { nric = g.Key, count = g.Count()})
                        .OrderByDescending(g => g.count)
                        .AsEnumerable()    //important to convert to Enumerable
                        .Select(c => c.ToExpando()); //convert to ExpandoObject
        return View(Visitor);
    }

Viewでは、@ model IEnumerable(モデルクラスではなく動的)では、匿名型オブジェクトをバインドするため、これは非常に重要です。

@model IEnumerable<dynamic>

@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
    <div>x=@item.nric, y=@item.count</div>
}

foreachの型ですが、varまたはdynamicを使用してもエラーは発生しません。

ちなみに、新しいフィールドに一致する新しいViewModelを作成すると、結果をビューに渡すこともできます。


0

再帰的な味になりました

public static ExpandoObject ToExpando(this object obj)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        new RouteValueDictionary(obj).ForEach(o => expandoObject.Add(o.Key, o.Value == null || new[]
        {
            typeof (Enum),
            typeof (String),
            typeof (Char),
            typeof (Guid),

            typeof (Boolean),
            typeof (Byte),
            typeof (Int16),
            typeof (Int32),
            typeof (Int64),
            typeof (Single),
            typeof (Double),
            typeof (Decimal),

            typeof (SByte),
            typeof (UInt16),
            typeof (UInt32),
            typeof (UInt64),

            typeof (DateTime),
            typeof (DateTimeOffset),
            typeof (TimeSpan),
        }.Any(oo => oo.IsInstanceOfType(o.Value))
            ? o.Value
            : o.Value.ToExpando()));

        return (ExpandoObject) expandoObject;
    }

0

ExpandoObject Extensionの使用は機能しますが、ネストされた匿名オブジェクトを使用すると中断します。

といった

var projectInfo = new {
 Id = proj.Id,
 UserName = user.Name
};

var workitem = WorkBL.Get(id);

return View(new
{
  Project = projectInfo,
  WorkItem = workitem
}.ToExpando());

これを達成するには、これを使用します。

public static class RazorDynamicExtension
{
    /// <summary>
    /// Dynamic object that we'll utilize to return anonymous type parameters in Views
    /// </summary>
    public class RazorDynamicObject : DynamicObject
    {
        internal object Model { get; set; }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (binder.Name.ToUpper() == "ANONVALUE")
            {
                result = Model;
                return true;
            }
            else
            {
                PropertyInfo propInfo = Model.GetType().GetProperty(binder.Name);

                if (propInfo == null)
                {
                    throw new InvalidOperationException(binder.Name);
                }

                object returnObject = propInfo.GetValue(Model, null);

                Type modelType = returnObject.GetType();
                if (modelType != null
                    && !modelType.IsPublic
                    && modelType.BaseType == typeof(Object)
                    && modelType.DeclaringType == null)
                {
                    result = new RazorDynamicObject() { Model = returnObject };
                }
                else
                {
                    result = returnObject;
                }

                return true;
            }
        }
    }

    public static RazorDynamicObject ToRazorDynamic(this object anonymousObject)
    {
        return new RazorDynamicObject() { Model = anonymousObject };
    }
}

コントローラーの使用法は、ToExpando()の代わりにToRazorDynamic()を使用することを除いて同じです。

匿名オブジェクト全体を取得するビューでは、最後に「.AnonValue」を追加するだけです。

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;

0

ExpandoObjectを試しましたが、次のようなネストされた匿名の複合型では機能しませんでした。

var model = new { value = 1, child = new { value = 2 } };

だから私の解決策はJObjectをビューモデルに返すことでした:

return View(JObject.FromObject(model));

.cshtmlでダイナミックに変換します。

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.