ViewBagに匿名型を入れるとRuntimeBinderException
ViewBag に匿名型を入れて View 側で使おうとすると、「'object' に 'Xxxxx' の定義がありません」みたいなエラー(RuntimeBinderException)になってしまいます。
開発環境は以下の通り。
- Windows 7 (64bit)
- Visual Studio 2012 Express for Web
- ASP.NET MVC 4
この問題について調べていたところ、StackOverFlowのスレッド に同じ現象が投稿されており、それを参考にして解決できたのできたのですが、日本語の情報が全くなくだいぶ苦労したのでここに記録しておきます。
問題のコードは以下の通り。
コントローラ側で匿名クラスをViewBagに入れ、ビュー側でそれを見に行く、というだけのことです。
/Controllers/HomeController.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcApplication8.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { ViewBag.Message = "Hello"; ViewBag.Object1 = new { String1 = "World" }; return View(); } } }
/Views/Home/Index.cshtml
@{ ViewBag.Title = "Index"; } <h2>Index</h2> <p>@ViewBag.Message</p> <p>@ViewBag.Object1.String1</p>
リフレクションを使うと?
試しにリフレクションを使って匿名型のプロパティを取り出してみたところと、問題なく取れました。
@{ ViewBag.Title = "Index"; } <h2>Index</h2> <p>@ViewBag.Message</p> @{ Type t = ViewBag.Object1.GetType(); var string1 = t.GetProperty("String1").GetValue(ViewBag.Object1); } <p>@string1</p>
でもこれではメンドクサ過ぎて解決になってない。
ちゃんとプロパティは入ってるんだから普通に参照できてほしいな〜
解決策
で、解決策として紹介されていたのは、dynamic オブジェクトを ExpandoObject に変換する方法でした。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Dynamic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcApplication8.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { ViewBag.Message = "Hello"; ViewBag.Object1 = new { String1 = "World" }.ToExpando(); // ViewBagに入れるとき匿名型をExpandoObjectに変換! return View(); } } public static class Extentions { 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; } } }
こうすることによって、View側で ViewBag.Object1.String1 がエラーにならず、期待通りの動きになります。