Search
Categories
Navigation
Twitter Updates

Twitter Updates

    follow me on Twitter
    « DevLink: ASP.NET MVC Patterns & Anti-Patterns | Main | Using Ninject 2 with ASP.NET MVC »
    Friday
    Aug142009

    Anonymous Types in ASP.NET MVC

    ASP.NET MVC tries its best to implement some development short-hand semantics that Ruby & Rails made popular and that other libraries (such as jQuery) have also adopted. One such pattern is the use of key-value pairs for method parameters, using defaults if values aren’t provided.

    For example, here’s a snippet from Rails:

    <%= link_to 'About', { :controller => 'help', :action => 'index' } %>

    And one from jQuery:

    $.ajax({
       type: "POST",
       url: "some.php",
       data: "name=John&location=Boston",
       success: function(msg){
         alert( "Data Saved: " + msg );
       }
    });

    MVC tends to use something similar with the Routing system:

    return RedirectToAction("Details", new {id = dinner.DinnerID});

    This kind of approach can make sense when emphasizing convention over configuration, since default values can be supplied by the framework but it’s possible to override values without making the API overly cumbersome. In contrast, the Virtual Earth Bing Maps API uses a more traditional approach that yields calls like this:

    map.Find("", where, null, null, null, null, null, false, null, null, callbackUpdateMapDinners);

    The downside (within a static language like C# at least) is the lack of type-checking and refactoring support. I tend to think that a clean API and the productivity offered through convention over configuration tend to outweigh these negatives in certain situations.

    All that said, what is MVC actually doing when you pass it one of these anonymous objects?

    It’s All About the String Dictionaries

    Since the System.Web.Mvc source is available, it’s easy to see what all the RedirectTo* methods are doing with these option objects. They all pass them to the RouteValueDictionary(object) constructor, like so:

    protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues) {
        return RedirectToAction(actionName, new RouteValueDictionary(routeValues));
    }

    RouteValueDictionary lives in the System.Web.Routing assembly, whose source has not been made available, so to continue our spelunking we’ll need to fire up Red Gate’s .Net Reflector.Red Gate .NET Reflector logo

    A quick aside about .NET Reflector: It’s a fairly well-known tool, but I’m always surprised at how many people do not know about it or simply never use it. This is as crazy as the many web developers who don’t use Firebug. Reflector is an essential tool when learning how .NET libraries work as well as trouble-shooting them. I’ve solved several tricky production problems using nothing but Reflector. More .NET developers should use it!

    If you haven’t loaded the MVC libraries into Reflector, you’ll need to do so before continuing. Assuming you’ve installed MVC, you’ll find the assemblies in the following locations. You can drag & drop the DLLs from Explorer into Reflector to load them:

    • System.Web.Abstractions: [Program Files]\Reference Assemblies\Microsoft\Framework\v3.5\System.Web.Abstractions.dll
    • System.Web.Mvc: [Program Files]\Microsoft ASP.NET\ASP.NET MVC 1.0\Assemblies\System.Web.Mvc.dll
    • System.Web.Routing: [Program Files]\Reference Assemblies\Microsoft\Framework\v3.5\System.Web.Routing.dll

    After loading the System.Web.Routing assembly into Reflector, we can search for the RouteValueDictionary class and find the constructor that accepts an object:

    public RouteValueDictionary(object values)
    {
        this._dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        this.AddValues(values);
    }

    The AddValues(object) method is performing the heavy lifting:

    private void AddValues(object values)
    {
        if (values != null)
        {
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
            {
                object obj2 = descriptor.GetValue(values);
                this.Add(descriptor.Name, obj2);
            }
        }
    }

    As you can see, RouteValueDictionary is using reflection to transform this object into a Dictionary<string, object> with the property name used as the key. This dictionary is then used by the rest of the framework to execute and resolve routing.

    Supporting Convention Over Configuration

    Such an implementation makes it easy for developers to pass a wide range of data into methods without the specific data elements being known at design time. It’s also possible to rely on default values (such as routing’s default controller and action values). System.Web.Mvc does this with RouteValueDictionary in the RouteValuesHelpers.MergeRouteValues() method:

    public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName, RouteValueDictionary implicitRouteValues, RouteValueDictionary routeValues, bool includeImplicitMvcValues) {
        // Create a new dictionary containing implicit and auto-generated values
        RouteValueDictionary mergedRouteValues = new RouteValueDictionary();
    
        if (includeImplicitMvcValues) {
            // We only include MVC-specific values like 'controller' and 'action' if we are generating an action link.
            // If we are generating a route link [as to MapRoute("Foo", "any/url", new { controller = ... })], including
            // the current controller name will cause the route match to fail if the current controller is not the same
            // as the destination controller.
    
            object implicitValue;
            if (implicitRouteValues != null && implicitRouteValues.TryGetValue("action", out implicitValue)) {
                mergedRouteValues["action"] = implicitValue;
            }
    
            if (implicitRouteValues != null && implicitRouteValues.TryGetValue("controller", out implicitValue)) {
                mergedRouteValues["controller"] = implicitValue;
            }
        }
    
        // Merge values from the user's dictionary/object
        if (routeValues != null) {
            foreach (KeyValuePair<string, object> routeElement in GetRouteValues(routeValues)) {
                mergedRouteValues[routeElement.Key] = routeElement.Value;
            }
        }
    
        // Merge explicit parameters when not null
        if (actionName != null) {
            mergedRouteValues["action"] = actionName;
        }
    
        if (controllerName != null) {
            mergedRouteValues["controller"] = controllerName;
        }
    
        return mergedRouteValues;
    }

    jQuery also provides similar, but more flexible, functionality with the jQuery.extend() function. These kind of merge functions are what allows this pattern to support convention over configuration so well.

    Evil or Not?

    Swimmer on beach with dangerous current; Photo by: owenfm (Flickr) There’s some negative commentary on this aspect of MVC, which often boils down to citing the magic string anti-pattern.

    I’m a bit less dogmatic about this point, seeing it as an approach that has drawbacks, like most do. While it’s true that it does hamper static refactoring tools and type checking, it also allows for a more productive developer experience. It seems to boil down to aesthetic preferences between those that prefer static type checking (despite its inherent verbosity) and those that prefer a more dynamic language approach that relies on unit testing to overcome the deficiencies.

    In other words, it’s just another tool for your toolbox and nothing more.

    PrintView Printer Friendly Version

    EmailEmail Article to Friend

    Reader Comments (2)

    Excellent post, cheers! I'm just wondering how you get to the source code of the AddValues method, .net reflector won't show it and based on a quick research it can't. Thanks.

    January 6, 2011 | Unregistered CommenterDiogo

    @Diogo:

    I just did this using .NET Reflector. Once you load the DLLs within Reflector, it should be able to show you the code. Within the post, I outlined how to do this for ASP.NET MVC 1.0, including where the files should exist on your machine.

    Hope that helps.

    January 6, 2011 | Registered CommenterKevin Rohrbaugh

    PostPost a New Comment

    Enter your information below to add a new comment.

    My response is on my own website »
    Author Email (optional):
    Author URL (optional):
    Post:
     
    Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>