回答:
私の意見では、内部プロパティを持つ匿名型は、.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>
}
私は関連する質問で答えを見つけました。回答は、David Ebboのブログ投稿「MVCビューに匿名オブジェクトを渡し、動的オブジェクトを使用してそれらにアクセスする」で指定されています
これは、匿名型が内部でコントローラーに渡されるため、宣言されているアセンブリ内からのみアクセスできるためです。ビューは個別にコンパイルされるため、動的バインダーはそのアセンブリー境界を超えることができないと不平を言います。
しかし、考えてみると、動的バインダーからのこの制限は実際にはかなり人為的なものです。プライベートリフレクションを使用しても、内部メンバーへのアクセスを妨げるものは何もありません(そう、中程度の信頼でも機能します)。そのため、デフォルトの動的バインダーは、CLRランタイムで許可されていることを実行させる代わりに、C#コンパイルルール(内部メンバーにアクセスできない場合)を強制する方法から外れています。
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;
}
匿名型からモデルを作成してから、匿名オブジェクトを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
私は通常、ほとんどのビューに強く型付けされたビューモデルをお勧めしますが、この柔軟性は便利な場合があります。
フレームワーク即興インターフェースを使用できますを、匿名タイプをでラップできます。
あなたはただ返すだけIEnumerable<IMadeUpInterface>
で、Linqの最後に.AllActLike<IMadeUpInterface>();
これは機能します。これは、匿名型を宣言したアセンブリのコンテキストでDLRを使用して匿名プロパティを呼び出すためです。
コンソールアプリケーションを作成し、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イベントで実行できます。私はこれについて中国語でブログ投稿しましたが、コードとスナップショットを読むだけでいいと思います。:)
受け入れられた回答に基づいて、私はコントローラでオーバーライドして、それを一般的かつ舞台裏で機能させるようにしました。
これがコードです:
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");
}
}
}
これで、匿名オブジェクトをモデルとして渡すだけで、期待どおりに動作します。
私はhttps://stackoverflow.com/a/7478600/37055から少し盗むつもりです
インストールパッケージdynamiteyを使用すると、これを行うことができます。
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
そして農民は喜ぶ。
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を作成すると、結果をビューに渡すこともできます。
再帰的な味になりました
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;
}
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;
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>