Los Techies : Blogs about software and anything tech!

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.

Kick It on DotNetKicks.com
Posted Jan 14 2009, 07:34 AM by bogardj
Filed under:

Comments

Mark Nijhof wrote re: A new breed of magic strings in ASP.NET MVC
on 01-14-2009 8:19 AM

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

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

-Mark

Mark Nijhof wrote re: A new breed of magic strings in ASP.NET MVC
on 01-14-2009 8:52 AM

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

Garry Shutler wrote re: A new breed of magic strings in ASP.NET MVC
on 01-14-2009 11:04 AM

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: blog.robustsoftware.co.uk/.../strongly-typed-links-for-aspnet-mvc.html

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

Garry Shutler wrote re: A new breed of magic strings in ASP.NET MVC
on 01-14-2009 11:08 AM

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 wrote re: A new breed of magic strings in ASP.NET MVC
on 01-14-2009 5:33 PM

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

ASP.NET MVC Archived Blog Posts, Page 1 wrote ASP.NET MVC Archived Blog Posts, Page 1
on 01-15-2009 1:00 AM

Pingback from  ASP.NET MVC Archived Blog Posts, Page 1

Reflective Perspective - Chris Alcock » The Morning Brew #265 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #265
on 01-15-2009 2:42 AM

Pingback from  Reflective Perspective - Chris Alcock  » The Morning Brew #265

alwn wrote re: A new breed of magic strings in ASP.NET MVC
on 01-15-2009 3:50 AM

Can't you do this?

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

bogardj wrote re: A new breed of magic strings in ASP.NET MVC
on 01-15-2009 7:15 AM

@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.

Dew Drop - January 15, 2009 | Alvin Ashcraft's Morning Dew wrote Dew Drop - January 15, 2009 | Alvin Ashcraft's Morning Dew
on 01-15-2009 9:32 AM

Pingback from  Dew Drop - January 15, 2009 | Alvin Ashcraft's Morning Dew

Alexis Kennedy wrote re: A new breed of magic strings in ASP.NET MVC
on 01-16-2009 9:51 AM

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

bogardj wrote re: A new breed of magic strings in ASP.NET MVC
on 01-16-2009 11:12 AM

@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.

gOODiDEA.NET wrote Interesting Finds: 2009 01.15 ~ 01.17
on 01-16-2009 9:36 PM

Web Web Design Trends For 2009 Why learning CSS is important in a (web) development world - Fizzler:

gOODiDEA wrote Interesting Finds: 2009 01.15 ~ 01.17
on 01-16-2009 9:40 PM

WebWebDesignTrendsFor2009WhylearningCSSisimportantina(web)developmentworld-...

Gilligan wrote re: A new breed of magic strings in ASP.NET MVC
on 01-27-2009 12:00 PM

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<CartController>(x => x.AddItem(new AddItem { UserId = user.Id});

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

bogardj wrote re: A new breed of magic strings in ASP.NET MVC
on 01-27-2009 1:05 PM

@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!

Serializing models for RouteValueDictionary and later model binding wrote Serializing models for RouteValueDictionary and later model binding
on 03-17-2010 4:47 PM

Pingback from  Serializing models for RouteValueDictionary and later model binding

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright Los Techies 2008, 2009. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems