web-dev-qa-db-ja.com

Razorの動的な匿名型によりRuntimeBinderExceptionが発生する

次のエラーが表示されます。

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

匿名の動的型を見ると、明らかにRatingNameがあります。

Screenshot of Error

Tupleを使用してこれを実行できることに気付きましたが、エラーメッセージが表示される理由を理解したいと思います。

154
JarrettV

私の意見では、内部プロパティを持つ匿名型は、.NETフレームワークの設計上の決定が不十分です。

以下は、この問題を解決するための迅速なNice extensionです。つまり、匿名オブジェクトを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>
}
237
Adaptabi

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

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

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

50
JarrettV

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

匿名型からモデルを作成し、匿名オブジェクトをこのように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

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

16
Simon_Weaver

フレームワーク impromptu interface を使用して、インターフェイスで匿名型をラップできます。

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

5
jbtule

コンソールアプリケーションを作成し、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サイトのビルド後イベントでプログラムを実行できます。 これに関する中国語のブログ投稿 を書きましたが、コードとスナップショットを読むことができると信じています。 :)

4
Jeffrey Zhao

受け入れられた答えに基づいて、コントローラーでオーバーライドし、一般的に、そして舞台裏で機能するようにしました。

コードは次のとおりです。

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

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

2
yoel halb

再帰的なフレーバーになりました

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
Donny V.

RuntimeBinderExceptionがトリガーされた理由は、他の投稿に良い答えがあると思います。実際にどのように機能させるかを説明することに焦点を当てています。

回答@DotNetWiseおよび ASP.NET MVCの匿名型コレクションを使用したビューのバインド を参照して、

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

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>[email protected], [email protected]</div>
}

Foreachのタイプ、varまたはdynamicを使用してもエラーはありません。

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

0
V-SHY

https://stackoverflow.com/a/7478600/37055 から少し盗むつもりです。

Install-package dynamitey を実行すると、次のことができます。

return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));

そして、小作人たちは喜ぶ。

0
Chris Marisic