A new breed of magic strings in ASP.NET MVC

One of the common patterns in Ruby on Rails is the use of hashes in place of large parameter lists.  Instead of hashes, which don’t exist in C#, ASP.NET MVC uses anonymous types for quite a few HTML generators on the view side.  For example, we might want to generate a strongly-typed URL for a specific controller action:

Url.Action<ProductsController>(x => x.Edit(null), new { productId = prodId })

Now, this is an example where our controller action uses some model binding magic to bind Guids to an actual Product.  We need to pass in route values to the helper method, which will eventually get translated into a RouteValueDictionary.

All special MVC terms aside, this is a trick used by the MVC team to simulate hashes.  Since the dictionary initializer syntax is quite verbose, with lots of angly-bracket cruft, anonymous types provide a similar effect to hashes.  However, don’t let the magic fool you.  Anonymous types used to create dictionaries are still dictionaries.

See that “productId” property in the anonymous object above?  That’s a magic string.  It’s used to match up to the controller parameter name.  Other anonymous types are used to add HTML attributes, supply route information, and others.  It’s subtle, as it doesn’t look like a string, it looks like an object.  There aren’t any quotes, it just looks like I’m creating an object.

However, it suffers from the same issues that magic strings have:

  1. They don’t react well to refactoring
  2. It’s rampant, pervasive duplication
  3. They’re easily broken with mispelling

If I change the name of the parameter in the Edit method, all code expecting the parameter name to be something specific will break.  This wouldn’t be so bad if I had to change just one location, but I need to fix all places that use that parameter (likely with a Replace All command).  I understand the desire to separate views from controllers, but I don’t want to go backwards and now have brittle views.

Luckily, there are some easy ways to solve this problem.

Option #1 – Just use the dictionary

One quick way to not use magical anonymous types is to just use the dictionary.  It’s a little more verbose, but at least we can apply the “Once and only once” rule to our dictionary keys – and store them in a constant or static field.  We still have use the dictionary initializer syntax:

Url.Action<ProductsController>(x => x.Edit(null), new RouteValueDictionary { { ProductsController.ProductId, prodId } })

The ProductId field is just a constant we declared on our ProductsController.  But at least it’s strongly-typed, refactoring-friendly and simple.  It is quite a bit bigger than our original method, however.

Option #2 – Strongly-typed parameter objects

Instead of an anonymous object, just use a real object.  The object represents the parameters of the action method:

public class EditProductParams
{
    public Guid product { get; set; }
}

The name of this property matches the name of our parameter in the controller.  In our ASPX, we’ll use this class instead of an anonymous one:

Url.Action<ProductsController>(x => x.Edit(null), new EditProductParams { product = prodId })

We solve the “Once and only once” problem…almost.  The parameter name is still duplicated on the controller action method and this new class.  I might define this class beside the controller to remind myself, but the parameter still shows up twice.

In other applications of the anonymous object, this option really wouldn’t work.  For things like Html.ActionLink, where an anonymous type is used to populate HTML attributes, the mere presence of these properties may cause some strange things to happen.  It works, but only partially.  If the designers of MVC wanted to create parameter objects, they probably would have.

Option #3 – Use Builders

We can use a Builder for the entire HTML, or just the RouteValueDictionary.  Either way, we use a fluent builder to incrementally build the HTML or parameters we want:

<%= Url.Action<ProductsController>(x => x.Edit(null, null), Params.Product(product).Customer(customer)) %>

Instead of hard-coded strings, we use a builder to chain together all the information needed to build the correct route values.  Our builder encapsulates all of the parameter logic in one place, so that we can chain together whatever parameters it needs.  It starts with the chain initiator:

public static class Params
{
    public static ParamBuilder Product(Product product)
    {
        var builder = new ParamBuilder();

        return builder.Product(product);
    }

    public static ParamBuilder Customer(Customer customer)
    {
        var builder = new ParamBuilder();

        return builder.Customer(customer);
    }
}

The actual building of the parameters is in our ParamBuilder class:

public class ParamBuilder
{
    private Product _product;
    private Customer _customer;

    public ParamBuilder Product(Product product)
    {
        _product = product;
        return this;
    }

    public ParamBuilder Customer(Customer customer)
    {
        _customer = customer;
        return this;
    }

Because each new parameter method returns “this”, we can chain together the same ParamBuilder instance, allowing the developer to build whatever common parameters needed.  Finally, we need to make sure our ParamBuilder can be converted to a RouteValueCollection.  We do this by supplying an implicit conversion operator:

public static implicit operator RouteValueDictionary(ParamBuilder paramBuilder)
{
    var values = new Dictionary<string, object>();

    if (paramBuilder._product != null)
    {
        values.Add("p", paramBuilder._product.Id);
    }

    if (paramBuilder._product != null)
    {
        values.Add("c", paramBuilder._customer.Id);
    }

    return new RouteValueDictionary(values);
}

The compiler will automatically call this implicit operator, so we don’t need any “BuildDictionary” or other creation method, the compiler does this for us.  This is just an example of a builder method; the possibilities are endless for a design of “something that creates a dictionary”.

Each of these approaches has their plusses and minuses, but all do the trick of eliminating that new breed of magic strings in ASP.NET MVC: the anonymous type.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://blog.fohjin.com Mark Nijhof

    Very interesting, and I agree. I like the builder option the most, but then as an addition to Url:

    Url.Action (x => x.Edit(null, null)).Product(product).Customer(customer)

    -Mark

  • http://blog.fohjin.com Mark Nijhof

    Ahh but that wouldn’t work for this situation I guess because you would have different params for each possible type. So then I would suggest #3

    -Mark

  • http://blog.robustsoftware.co.uk Garry Shutler

    I’ve created refactoring friendly links utilising expressions which cuts out a lot of fluff from the links themselves.

    I wrote a blog post about the initial version here: http://blog.robustsoftware.co.uk/2008/11/strongly-typed-links-for-aspnet-mvc.html

    It’s changed slightly now, but the underlying principles are the same.

  • http://blog.robustsoftware.co.uk Garry Shutler

    Gah, sorry, missed the fact that you are using model binding so that your actions take objects instead of the id. That makes my previous comment pointless. Carry on, nothing to see here.

  • Paco

    There is a safe Html.ActionLink in the “futures” dll. A disadvantage is the performance penalty of Lamba.Compile.

  • alwn

    Can’t you do this?

    Url.Action (x => x.Edit(prodId))

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @alwn

    Not when Edit takes a Product and not a GUID as the parameter. We’re using model binding to automagically pull our entities out of the database.

  • Alexis Kennedy

    Nice, but aren’t those still magic strings (“p”, “c”) lurking in the conversion operator? Or am I missing something?

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Alexis

    Yes, because they have to match up to parameter names for action methods. But they only appear once, so I’ll only have to fix one place if something breaks. Better, but not perfect.

  • Gilligan

    Why not create a dummy viewmodel in the lambda and set whatever values you want it to use there?
    Then when you parse the expression extract any values that are not equal to the default value for that datatype.
    Example:
    this.Action(x => x.AddItem(new AddItem { UserId = user.Id});

    Rhino Mocks does something similar to this for argument constraints vie Args where the parameter object is just a recorder of values.

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Gilligan

    That helps for building URL parameters, but not so much on the other HTML Helpers. I’ll take a look at this one for our entities, though. Thanks!

  • Pingback: Get rid of those Magic Strings! Creating ViewModel properties using Lambda Expresssions with MVVM Light Toolkit’s ObservableObject class | www.davidarodriguez.com