Search code examples
.netasp.net-mvcc#-4.0dynamicanonymous-types

Dynamic view of anonymous type missing member issue - MVC3


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?


Solution

  • 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.