Dynamic Anonymous type in Razor causes RuntimeBinderException

61,930

Solution 1

Anonymous types having internal properties is a poor .NET framework design decision, in my opinion.

Here is a quick and nice extension to fix this problem i.e. by converting the anonymous object into an ExpandoObject right away.

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;
}

It's very easy to use:

return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());

Of course in your view:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}

Solution 2

I found the answer in a related question. The answer is specified on David Ebbo's blog post Passing anonymous objects to MVC views and accessing them using dynamic

The reason for this is that the anonymous type being passed in the controller in internal, so it can only be accessed from within the assembly in which it’s declared. Since views get compiled separately, the dynamic binder complains that it can’t go over that assembly boundary.

But if you think about it, this restriction from the dynamic binder is actually quite artificial, because if you use private reflection, nothing is stopping you from accessing those internal members (yes, it even work in Medium trust). So the default dynamic binder is going out of its way to enforce C# compilation rules (where you can’t access internal members), instead of letting you do what the CLR runtime allows.

Solution 3

Using ToExpando method is the best solution.

Here is the version that doesn't require System.Web assembly:

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;
}

Solution 4

Instead of creating a model from an anonymous type and then trying to convert the anonymous object to an ExpandoObject like this ...

var model = new 
{
    Profile = profile,
    Foo = foo
};

return View(model.ToExpando());  // not a framework method (see other answers)

You can just create the ExpandoObject directly:

dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;

return View(model);

Then in your view you set the model type as dynamic @model dynamic and you can access the properties directly :

@Model.Profile.Name
@Model.Foo

I'd normally recommend strongly typed view models for most views, but sometimes this flexibility is handy.

Solution 5

You can use the framework impromptu interface to wrap an anonymous type in an interface.

You'd just return an IEnumerable<IMadeUpInterface> and at the end of your Linq use .AllActLike<IMadeUpInterface>(); this works because it calls the anonymous property using the DLR with a context of the assembly that declared the anonymous type.

Share:
61,930

Related videos on Youtube

JarrettV
Author by

JarrettV

I'm a senior software architect for a healthcare startup in the south.

Updated on January 18, 2020

Comments

  • JarrettV
    JarrettV over 4 years

    I'm getting the following error:

    'object' does not contain a definition for 'RatingName'

    When you look at the anonymous dynamic type, it clearly does have RatingName.

    Screenshot of Error

    I realize I can do this with a Tuple, but I would like to understand why the error message occurs.

  • Buildstarted
    Buildstarted over 13 years
    Beat me to it :) I ran into this problem with my Razor Engine (the precursor to the one on razorengine.codeplex.com )
  • Adaptabi
    Adaptabi about 13 years
    This is not really an answer, not saying more about the "accepted answer" !
  • Lucas
    Lucas about 13 years
    @DotNetWise: It explains why the error ocurrs, which was the question. You also get my upvote for providing a nice workaround :)
  • Chris Marisic
    Chris Marisic about 13 years
    +1 I was specifically looking for HtmlHelper.AnonymousObjectToHtmlAttributes I knew this absolutely had to baked in already and didn't want to reinvent the wheel with similar handrolled code.
  • GONeale
    GONeale about 13 years
    What is the performance like on this, compared to simply making a strongly typed backing model?
  • Jeremy Boyd
    Jeremy Boyd about 13 years
    @DotNetWise, Why would you use HtmlHelper.AnonymousObjectToHtmlAttributes when you can just do IDictionary<string, object> anonymousDictionary = new RouteDictionary(object)?
  • Adaptabi
    Adaptabi about 13 years
    I have tested HtmlHelper.AnonymousObjectToHtmlAttributes and works as expected. Your solution can also work. Use whichever seems easier :)
  • Andrew
    Andrew almost 13 years
    Awesome little trick :) Don't know if it is any better than just a plain class with a bunch of public properties though, at least in this case.
  • Johny Skovdal
    Johny Skovdal over 12 years
    If you want it to be a permanent solution, you could also just override the behavior in your controller, but it requires a few more workarounds, like being able to identify anonymous types and creating the string/object dictionary from the type by yourself. If you do that though, you can override it in: protected override System.Web.Mvc.ViewResult View(string viewName, string masterName, object model)
  • Adaptabi
    Adaptabi over 12 years
    @Jeremy Your solution is not working for html5 like attributes i.e. new { data_name = "" } - would be converted to data-name="" (which is the client side expected behavior. On our discussion here, really your solution is better as the intented behavior is to have ViewBag.data_name - a valid C#/razor expression - rather than ViewBag.data-name I have updated the answer
  • aruno
    aruno over 11 years
    FYI: this answer is now very much out of date - as the author says himself in red at the beginning of the referenced blog post
  • aruno
    aruno almost 11 years
    @yohal you certainly could - I guess it's personal preference. I prefer to use ViewBag for miscellaneous page data generally unrelated to the page model - maybe related to the template and keep Model as the primary model
  • yoel halb
    yoel halb almost 11 years
    BTW you don't have to add @model dynamic, as it is the default
  • Cristian Diaconescu
    Cristian Diaconescu almost 11 years
    @Simon_Weaver But the post update doesn't explain how it should work in MVC3+. - I hit the same problem in MVC 4. Any pointers on the currently 'blessed' way of using dynamic?
  • Den
    Den almost 11 years
    It's a better answer. Not sure if like what HtmlHelper does with underscores in the alternative answer.
  • Randall Borck
    Randall Borck over 10 years
    This answer helped me, but the other part that got me was that I needed to update the WebViewPage to specify that the model is dynamic. This can be done by modifying the .cshtml inherits to the following: @inherits WebViewPage<dynamic>
  • codenheim
    codenheim about 10 years
    +1 for general purpose answer, this is useful outside of ASP / MVC
  • h-rai
    h-rai over 9 years
    exactly what i needed, implementing the method to convert anon objs to expando objects was taking too much time...... thanks heaps
  • sports
    sports over 9 years
    What if my .Select uses a pre defined projection? eg: someLinq.Select(preDefinedProjection().ToExpando()) where preDefinedProjection() returns a Expression<Func<SomeType, dynamic>>
  • sports
    sports over 9 years
    what about nested dynamic properties? they will continue to be dynamic... eg: `{ foo: "foo", nestedDynamic: { blah: "blah" } }
  • Triynko
    Triynko almost 9 years
    Same problem in MVC 5.2.3!!! The ExpandoObject solution that uses a RouteDataValues object to extract the members with reflection works. It truly is an artificial boundary imposed by something in Razor, as there is precisely nothing stopping this conversion from executing in the very same context that the error occurs.
  • ngless
    ngless about 8 years
    Another point that should be added is that if you're working with a property on a Model rather than the Model itself, you may need to cast your property to dynamic I.E. public class Model { public IEnumerable<ExpandoObject> Property { get; set; } and then in your view: @foreach ( dynamic item in Model.Property) @item.anonPropName