Udostępnij za pośrednictwem


Passing anonymous objects to MVC views and accessing them using dynamic

Note (12/22/2011): now that MVC 3 has direct support for dynamic, the technique below is no longer necessary. This post is in fact what led to integrating the feature into MVC!

First, I’ll start with a little disclaimer: this post is not about whether using dynamic is better/worse than static typing.  Instead, it’s about making it more convenient to use dynamic if you choose to go that route.  Clearly, some people dislike dynamic, as you can see in the comments in that post from Phil Haack, and for the most part, all the key arguments for/against have been made.

So anyway, let’s proceed… Recently, a few people have experimented with extending their view pages from ViewPage<dynamic>.  The idea is to then be able to access model data using the more convenient dynamic syntax.  e.g. check out this thread on StackOverflow, as well as Phil’s post I mention above.

One limitation that people are hitting is that you can’t easily pass an anonymous object as your model, because anonymous types are internal.

What we’re trying to write

e.g. Let’s say we’re trying to write this code in the controller:

 public class HomeController : Controller {
    public ActionResult Index() {
        return View(
            new {
                Message = "Welcome to ASP.NET MVC!",
                Date = DateTime.Now
            });
    }
}

Note that we’re passing an anonymous object as the model.  The main reason to do this is to avoid the need to create an explicit ViewModel type.  Obviously, that’s controversial, but it should be viewed more as a simpler alternative to writing

 ViewData["Message"] = "Welcome to ASP.NET MVC!";
ViewData["Date"] = DateTime.Now;
return View();

And then you’d change your view to have Inherits="System.Web.Mvc.ViewPage<dynamic>", which ideally would let you write something like this:

 <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2><%: Model.Message %></h2>
    <p>
        The date is <%: Model.Date %>
    </p>
</asp:Content>

 

Which is simpler than having to dig things out of the view data dictionary through string indexing.

But the default dynamic binder won’t let us!

Unfortunately, if you try to run this code, it’ll blow up with this error: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: '<>f__AnonymousType1<string,System.DateTime>.Message' is inaccessible due to its protection level

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: write our own!

I’m not sure why it was designed this way, but the good news is that it’s easy to work around by writing our own custom DynamicObject which binds through private reflection!  I’ve written a couple posts that make use of custom DynamicObjects (find them here), and the basic concept is the same: given the name of a property, write whatever code you want to produce its value.

Here, we’re not only going to write a custom Dynamic Object, but also a custom DynamicViewPage that uses it.  And doing all this takes surprisingly little code.  Here is the full implementation:

 public class DynamicViewPage : ViewPage {
    // Hide the base Model property and replace it with a dynamic one
    public new dynamic Model { get; private set; }

    protected override void SetViewData(ViewDataDictionary viewData) {
        base.SetViewData(viewData);

        // Create a dynamic object that can do private reflection over the model object
        Model = new ReflectionDynamicObject() { RealObject = ViewData.Model };
    }

    class ReflectionDynamicObject : DynamicObject {
        internal object RealObject { get; set; }

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            // Get the property value
            result = RealObject.GetType().InvokeMember(
                binder.Name,
                BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                RealObject,
                null);

            // Always return true, since InvokeMember would have thrown if something went wrong
            return true;
        }
    }
}

As you can see there is not that much to it: when we need to look up a property value, we use private reflection and that’s the end of it.

And now it all works!

Once we have this, all that’s left to do is use it as our base class for the view, e.g.

 <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="MvcHelpers.DynamicViewPage" %>

And we’re then able to write exactly what we wanted above in the controller and the view, without hitting any access issues.

The full VS2010 project is attached to this post.

MvcDynamicViewPage.zip

Comments

  • Anonymous
    December 19, 2009
    @ali: sorry, this requires VS 2010 because it relies on the new C# dynamic feature which didn't exist before.  Note that you can download VS 2010 Beta 2 for free if you'd like to try it.

  • Anonymous
    December 19, 2009
    nop my bad to missed the dynamic keyword. thanks again for the great post .

  • Anonymous
    December 20, 2009
    That is really cool. They should replace ViewData with this little gem. Great Job.

  • Anonymous
    December 20, 2009
    I agree with Khalid this would be awesome as the default.

  • Anonymous
    December 20, 2009
    so can I apply this "dynamic goodness" to my MVVM structure in Silverlight is some way?

  • Anonymous
    December 20, 2009
    Very cool. Instead of letting it throw an exception, I capture it and then display all the available properties in the exception message or.... Depending on configuration I suppress the error and return empty string. (eg. for TempData)

  • Anonymous
    December 20, 2009
    Because this uses Reflection, is this going to result in a slower databinding?  This is certainly convenient compared to using ViewData and it's inherit danger of typing the keys in incorrectly, but I worry that using reflection will result in performance degradation?

  • Anonymous
    December 20, 2009
    @adefwebserver: I'm less familiar with Silverlight scenarios, but this technique is not specific to MVC, so I suspect that there are good ways to make use of C# 4.0 dynamic there as well.

  • Anonymous
    December 20, 2009
    @Adam: yes, these are good suggestions. It probably makes sense to make the default behavior return empty string instead of throwing. Otherwise, dealing with optional data becomes tricky.

  • Anonymous
    December 20, 2009
    @Sharbel: I haven't done any perf measurement, so I don't have hard numbers.  The reflection call will indeed be slower that the dictionary lookup.  However, it's not clear whether this will result in measurable perf degradation of the overall request.  I would be interesting to try measuring it.

  • Anonymous
    December 20, 2009
    Couldn't you just InternalsVisibleTo the MVC dll?  You'd have to sign your assembly, of course...

  • Anonymous
    December 21, 2009
    @WillSullivan: the difficult part is that the views are compiled on the fly, and you cannot predict what assembly they will be in. This makes it hard to put an InternalVisibleTo attribute on the main assembly. As an aside, I haven't verified that this would allow the default binder to work, though it certainly would make sense.

  • Anonymous
    December 21, 2009
    A slight idea, but it may result in exception after deploy

  • Anonymous
    December 21, 2009
    The comment has been removed

  • Anonymous
    December 22, 2009
    @Jack: I'm not sure what you mean by 'exception after deploy'.  Can you clarify?

  • Anonymous
    December 22, 2009
    @Cain: yes, ExpandoObject is sort of the 'standard' approach, and it is certainly an option.  The main point of this post is that I really wanted to find a way to pass in regular C# anonymous objects, which doesn't work without extra effort.

  • Anonymous
    December 22, 2009
    Umm just use  Inherits="System.Web.Mvc.ViewPage<object>" and use the templating in MVC2. Also when using dynamic you cannot make an instance. EX. dynamic d = new dynamic(); \ is incorrect. Instead you have to use dynamic d = new Object(); d.Name = "";

  • Anonymous
    December 22, 2009
    @Charlie: I don't see how MVC2 templating will help with dealing with anonymous objects. Also, you won't be able to use dynamic as you describe, since you can't add properties to Object.  Instead, look at ExpandoObject if you want to do this.

  • Anonymous
    December 23, 2009
    Yes you can add Properties to an Object as long as you declare it initially with dynamic. Go try it, I use it all the time.

  • Anonymous
    December 23, 2009
    @Charlie: I just tried the exact two lines you have above in a console app, and it blew up with: "'object' does not contain a definition for 'Name'".  Are you doing anything differently?

  • Anonymous
    December 23, 2009
    @davidebb Im Installing Beta2 on my work machine, get back to you soon.

  • Anonymous
    March 01, 2011
    impromptu-interface generates and proxy the implements and interface on the fly that forwards calls using the dlr (including changing the context so that it works on anonymous types). Sure you'd have to create an interface and there is a small one time penalty the first time a proxy is created for a type/interface but otherwise for each property call it ends up being 1 virtual invocation + 1 dlr invocation vs 1 dlr invocation + 1 reflection invocation so it balances out pretty quickly since reflection is very slow and uncached. . code.google.com/.../impromptu-interface

  • Anonymous
    December 21, 2011
    Hi, How to do it working in a ViewUserControl? Thanks.

  • Anonymous
    December 22, 2011
    @Dominik: MVC3 now directly supports dynamic, so the technique described here is no longer necessary.

  • Anonymous
    April 11, 2012
    If you still maintain .Net1.1/2.0/3.5 projects, the technique is necessary...

  • Anonymous
    November 11, 2013
    I was facing same problem and after few hours of struggled, I made it and shared it as an article. This solution is in ASP.NET 4+ www.dotnetfunda.com/.../binding-views-with-anonymous-type-collection-in-aspnet-mvc Hope this will help someone.