Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 557
Andras Zoltan Avatar asked Sep 17 '25 18:09

Andras Zoltan


2 Answers

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.

like image 134
Andras Zoltan Avatar answered Sep 20 '25 08:09

Andras Zoltan


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(!)

You cannot have such prerogative. Sorry, there is absolutely no excuse for this :-) Not to mention that what you are trying to achieve is impossible because dynamic types are internal to the declaring assembly. Razor views are compiled into a separate dynamic assembly at runtime by the ASP.NET engine.

So back to the topic: never pass anonymous objects as models to views. Always define use view models. Like this:

public class MyViewModel
{
    public int Value { get; set; }
}

and then:

public ActionResult Index()
{
    var model = Enumerable.Range(1, 10).Select(i => new MyViewModel { Value = i });
    return View(model);
}

and then use a strongly typed view:

@model IEnumerable<MyViewModel>
@Html.DisplayForModel()

and inside the corresponding display template which will automatically be rendered for each element of the collection so that you don't have to write any loops in your views (~/Views/Shared/DisplayTemplates/MyViewModel.cshtml):

@model MyViewModel
@:Number: @Html.DisplayFor(x => x.Value)

Things that we have improved from the initial version:

  • We have strongly typed views with Intellisense (and if you activate compilation for views even compile time safety)
  • Usage of strongly typed view models adapted to the specific requirements of your views.
  • Getting rid of ViewBag/ViewData which imply weak typing
  • Usage of display templates which avoid you writing ugly loops in your views => you rely on conventions and the framework does the rest
like image 42
Darin Dimitrov Avatar answered Sep 20 '25 09:09

Darin Dimitrov