I have an MVC3 site that I've setup for testing another site - most of it has been quick and dirty, and so I've not gone to town creating model and view model types for all the views - only where input is requried from the user.
Okay so I have a controller method that projects a Linq sequence and sets it into ViewBag
.
ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new { Value = i });
In my view (Razor C#) I then want to read this - quite simple:
@foreach(dynamic item in ViewBag.SomeData)
{
@:Number: @item.i
}
Except, of course, I get a RuntimeBinderException
because the anonymous type created in the controller is internal to the web project's output assembly and the actual Razor code here will be running in a different assembly generated by the build manager so, all in all DENIED!
Obviously a 'proper' model type would solve the issue - but let's say I simply don't want to do that because that's my prerogative(!) - how best to keep the code to a minimum and retain the dynamic-ness here?
This is what I've done (and I stress this only really addresses the issue adequately for anonymous types); lifting the members out and pushing them into an ExpandoObject
.
My initial change was to make the projection a multi-statement that returns an ExpandoObject
:
ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> {
var toReturn = new ExpandoObject();
toReturn.Value = i;
return toReturn;
});
Which is almost as short as the anonymous type just not as clean.
But then I wondered if I could grab the publicly readable members out of the anonymous type (the type is internal, but the properties it generates are public):
public static class SO7429957
{
public static dynamic ToSafeDynamic(this object obj)
{
//would be nice to restrict to anonymous types - but alas no.
IDictionary<string, object> toReturn = new ExpandoObject();
foreach (var prop in obj.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead)) // watch out for types with indexers
{
toReturn[prop.Name] = prop.GetValue(obj, null);
}
return toReturn;
}
}
Which means I can then use my original code - but with a little extension method call tagged on the end:
ViewBag.SomeData=Enumerable.Range(1,10).Select(i=> new { Value = i }.ToSafeDynamic());
It's not efficient, and it should definitely not be used for serious production code. But it's a good time saver.