OdeToCode IC Logo

KISS Your ASP.NET MVC Routes

Monday, January 25, 2010 by K. Scott Allen

A little bit of thinking and compromise can remove unnecessary complexity from the routes in an MVC application.

For example:

Morgan’s web site has authenticated users,and Morgan decides the URLs for managing users should look like /morgan/detail. Morgan adds the following code to register the route:

routes.MapRoute(
    "User",
    "{username}/{action}",
    new { controller ="User", action="Index" }
);

Morgan runs some tests and it looks like there is a problem. The User controller is getting requests for /home/index. Oops, that request should have gone to the Home controller. Fortunately, Morgan knows there is all sorts of whizz-bang features you can add to a route, so Morgan creates a route constraint:

public class UserRouteConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, 
                      Route route, string parameterName, 
                      RouteValueDictionary values, 
                      RouteDirection routeDirection)
    {
        var username = values["username"] as string;
        return IsCurrentUser(username);
    }

    private bool IsCurrentUser(string username)
    {
        // ... look up the user
    }
}

The constraint prevents the User route from gobbing requests to other controllers. It only allows requests to reach the User controller when the username exists in the application.

Morgan tweaks the route registration code to include the constraint:

routes.MapRoute(
    "User",
    "{username}/{action}",
    new { controller ="User", action="Index"},
    new { user = new UserRouteConstraint() }
);

Morgan runs some tests and everything works!

Well …

Everything works until a user registers with the name “home”. Morgan goes off to tweak the code again...

STOP!

As a rule of thumb I try to:

  • Never use advanced routing features like constraints
  • Always stick to a URL like {controller}/{action}/{id} or {controller}/{id}/{action}

Sometimes you have to go break those rules, but let’s look at Morgan’s case. If Morgan was happy with a URL like /user/index/morgan, then Morgan wouldn’t need to register any additional routes. The default routing entry in a new MVC application would happily invoke the index action of the User controller and pass along the username as the id parameter. Morgan could also go with /user/morgan and use a simple route entry like this:

routes.MapRoute(
    "User",
    "User/{username}/{action}",
    new { controller ="User", action="Index", username="*"}
);

No constraints required!

The KISS principle (keep it simple stupid) is a good design principle for routes. You’ll have fewer moving parts, consistent URL structures, and make the code easier to maintain.

Silverlight and ASP.NET MVC Don’t Serve the Same Master

Tuesday, January 19, 2010 by K. Scott Allen

contrast I’m flipping between MVC and Silverlight projects when a startling contrast starts to emerge. It’s not the obvious contrast between stateful and stateless. It’s a subtler contrast in design.

If you poke around in a new MVC project, one of the first pieces of code you’ll run across is the code to register the default route. It’s in global.asax.cs, and looks like this:

routes.MapRoute(
    "Default",                                            
    "{controller}/{action}/{id}",                         
    new { controller = "Home", action = "Index", id = "" }
);

This snippet relies on some of the recent features added to C# – extension methods and anonymously typed objects. It obviously draws some inspiration from Ruby on Rails, which builds on top of the dynamic, malleable Ruby language.

Compare that code to the code you’ll see when you open up just about any Silverlight control.

silverlight code

Well, you don’t actually see much code because the code is swept underneath a carpet of regions. This happens quite often in Silverlight, and I think it’s because developers are embarrassed to write lines and lines and lines of code to define a single simple property. It’s the elegance of RPG combined with the verbosity of COBOL.

public bool IsDropDownOpen
{
    get { return (bool)GetValue(IsDropDownOpenProperty); }
    set { SetValue(IsDropDownOpenProperty, value); }
}

public static readonly DependencyProperty IsDropDownOpenProperty =
    DependencyProperty.Register(
        "IsDropDownOpen",
        typeof(bool),
        typeof(Foo),
        new PropertyMetadata(false, OnIsDropDownOpenPropertyChanged));

 

I realize IsDropDownOpen isn’t just a simple property. It’s a dependency property, and dependency properties hold many great and magical powers. Data binding in Silverlight (and WPF), for example, is wonderfully feature rich and easy, but it comes with a price. Instead of writing one line of code …

public bool IsDropDownOpen { get; set; }

we need twelve!

The contrast isn’t in the number of lines of code, however. The contrast is in who the frameworks were designed to serve.

MVC is for Developers – Silverlight is for Tools

The MVC framework is designed for developers who write code. This fact is obvious in the API design and how the team fights for programmer friendly features like the new HTML encoding block syntax. The MapRoute code we saw earlier is just one example of many. The MVC framework took inspiration from platforms that are popular today because they favor convention over configuration and essence over ceremony. These are fancy terms for keeping things simple and readable.

Silverlight is designed for tooling. Visual Studio, Expression Blend, and other XAML, code, and art editors. I’m not saying we get rid of the tools. We need the tools for the feedback and capabilities they provide when building effects, animations, and irregularly shaped objects. But, it seems the developer’s life gets more difficult with the addition of every feature.

The Next 5 Years

I’d be surprised if another language doesn’t come along to become the de facto standard for the code behind XAML files. A language that is just as friendly to programmers as it is to XAML. A language that makes writing code for System.Windows fun, simple, and readable.  Maybe it will look like …

depprop bool IsDropDownOpen { get; set; }

… but smarter people then me can figure out the syntax. I just know there has to be something better.

Drop-down Lists and ASP.NET MVC

Monday, January 18, 2010 by K. Scott Allen

Working with drop-down lists in ASP.NET MVC has some confusing aspects, so let’s look at an example.

Imagine the goal is to edit a song (not the music and lyrics of a song – just the boring data pieces). Each song is associated with an album, and each song has a title and track number. With this description, you can imagine an edit view using the following code:

<%= Html.DropDownList("AlbumId", Model.Albums)%>
  ...
<%= Html.TextBox("Title", Model.Title) %>
   ...
<%= Html.TextBox("TrackNumber", Model.TrackNumber) %>

The Html.DropDownList helper method likes to work with SelectListItem objects, so a view model you can pair with this view looks like the following:

public class EditSongViewModel
{        
    public string Title { get; set; }                
    public int TrackNumber { get; set; }
    public IEnumerable<SelectListItem> Albums { get; set; }
}

Some people don’t like to use SelectListItem types in their view models. Instead, they’ll convert to them in the view. I think it’s entirely reasonable to use SelectListItem in a view model, because the view model is supposed to make the view easier to write. Having the view model perfectly aligned with the needs of the view means you need to think less (and write less code) when creating the view.

Creating SelectListItems

There are a couple approaches you can take when creating a sequence of SelectListItem objects. I think the cleanest approach is to have an extension method that knows how to take a collection of objects in your software (like Album objects), and map them into SelectListItem objects.

public static IEnumerable<SelectListItem> ToSelectListItems(
              this IEnumerable<Album> albums, int selectedId)
{
    return 
        albums.OrderBy(album => album.Name)
              .Select(album => 
                  new SelectListItem
                  {
                    Selected = (album.ID == selectedId),
                    Text = album.Name,
                    Value = album.ID.ToString()
                   });
}

You can use the method like this:

model.Albums = _repository.FindAllAlbums().ToSelectItems(selectedId);

That code works, because Html.DropDownList will happily work with IEnumerable of SelectListItem.

The class you need to be careful with is the SelectList class. I’ve seen quite a few people make the mistake of wrapping their SelectListItem objects in a SelectList without setting the DataTextField and DataValueField properties. This does not work:

model.Albums = new SelectList(
                _repository.FindAllAlbums().ToSelectListItems(1)
                );

You’d think the SelectList class would know how to work with a collection of SelectListItem objects - but it doesn’t. The following doesn’t work either (the drop-down list will display “System.Web.Mvc.SelectListItem” for every entry):

model.Albums = 
    new SelectList(_repository.FindAllAlbums()
                              .ToSelectItems(selectedID));

The SelectList class is really designed to perform the conversion we did earlier (with the extension method), but it uses late binding reflection. The following would work, because we tell the SelectList where to find the text and value fields:

model.Albums = new SelectList(
                _repository.FindAllAlbums(), "ID", "Name"
                );

Reading the Selection

If you are using the same model to accept input from the edit view during a postback, you might think the default model binder will repopulate the Albums collection with all the album information and set the selected album. Unfortunately - the web doesn’t work this way and the Albums collection will be empty.

The only album related information the browser will post is the value of the selected item. If you want this value bound to a model, you’ll need to provide an AlbumId property (to match the name we gave the DropDownList in the view - “AlbumId”).

public class EditSongViewModel
{
    public int AlbumId { get; set; }
    public string Title { get; set; }                
    public int TrackNumber { get; set; }
    public IEnumerable<SelectListItem> Albums { get; set; }
}

Some people will create two separate view models in this case. One view model is designed to carry information to the view, and will have an Albums property (but no AlbumId property). The second view model is designed to accept user input during postback and will have an AlbumId propery (but no Albums property). This approach adds the overhead of an extra class, but the view models are perfectly aligned with their duties and no properties go unused. You’ll have to decide which approach is best for you.

Summary

  • Don’t use a SelectList without telling it the DataTextField and DataValueField properties to use.
  • Don’t expect to see a collection for a drop-down list repopulated on a postback.
  • Extension methods make it easy to create a sequence of SelectListItem objects in strongly-typed code.
  • Html.DropDownList doesn’t require a SelectList – it’s happy working with any sequence of SelectListItem objects.

T4MVC in MSDN Magazine

Thursday, January 14, 2010 by K. Scott Allen

msdn january 2010 The January 2010 issue of MSDN Magazine is online with my article covering T4MVC:

Microsoft Visual Studio includes a code generation engine known as T4 (which is short for Text Template Transformation Toolkit). You’ve probably already used T4 templates in Visual Studio without even knowing they were working behind the scenes. In this article I’m going to give you a basic introduction to T4 templates and show you how ASP.NET MVC uses this technology. I’ll also show you how to customize T4 templates to enhance your day-to-day work with the MVC framework

Thanks go to David Ebbo for his help and comments on the article. David continues to improve  T4MVC, for example: T4MVC 2.6.10: fluent route value API, shorter way to refer to action, and more

Enjoy!

Of Web Browsers and Humanity

Wednesday, January 13, 2010 by K. Scott Allen

Douglas Crockford posted an interesting topic for discussion on his site (look for the Discussion Topic section at the bottom of the page):

If a web browser is defective, causing errors in the display or performance of the page, should the page developer struggle to hide the browser's defects, or should the defects be revealed in hope of creating market pressure to force the browser maker to make good? By which approach is humanity better served?

What I’d Like To Say

When I was a kid, I loved Mad Libs. And I bet you could come up with a Mad Lib that represents a style of question every web developer asks on a regular basis. I think it would look like this:

I’m trying to ______ a ______ and it’s fine in ______ and _______, but in ______ it doesn’t _______ing work.

It’s always the same question - you just have to fill in the blanks with 1 CSS property, 1 DOM element, 3 browser versions, and one profanity. Isn’t it insane? Can’t we start a revolution and all make a New Year’s resolution?

This year I will not write hacky workaround code for defective browsers

But …

It’s a business decision. I think this is what gets under the collective skin of the web development community more than anything else. We can’t make a resolution to stop! We already told the bosses they’ll spend an extra $10,000 to support IE6, but they are more than happy with the return on the investment. So, we sulk back to our offices and grudgingly add some more conditional CSS comments to the site’s style sheet.

If there is going to be market pressure, it won’t come from developers per se, but from big Internet properties with fanatical followers, like Facebook, Amazon, eBay, and Twitter. They have the power to force users to move, and when users start moving the makers react.

And yes, I think it is in the best interest of humanity.

Don&rsquo;t Let Your Code Marry An Axe Murderer

Tuesday, January 12, 2010 by K. Scott Allen

272/365 It’s a lesson I learned from the school of hard knocks: be careful about the shady characters you let into your software.

There are lots of software frameworks, components, and tools in the world – and they all want you to think you can live with them happily ever after. They seduce you with the promise of productivity, then hack you to death once they’ve gained your trust.

Here are a few questions I’ve learned to ask when trying to spot next year’s problems:

1. How easy is it to deploy?

I’ve been burned by software that is difficult to move from one environment to the next. Some products, particularly server products, assume you never need to move software into different environments (like development, test, productions). Avoid anything you can’t automate!

Software should be as easy to deploy as possible, otherwise it doesn’t get deployed often enough.

2. Will it work in source control?

Some tools and designers generate files that don’t work well under source control. If you can’t diff and merge you lose a tremendous amount of flexibility when it comes time to branch, patch, and support concurrent development. Database tools are historically notorious for having problems in this area because they tend to design for DBAs instead of developers.

It is, however, perfectly reasonable to check in binary dependencies.

3. Is it needy?

Speaking of dependencies – they are a touchy subject. You can’t invoke the “not invented here” rule in every decision without writing your own compiler. At the same time, every dependency you introduce to a product is a calculated risk. How many dependencies does this new thing have? Will you ever delay shipping because you are waiting for a new version of a dependency? Will you ever delay shipping because you are waiting for a new version of a dependency’s dependency? How many things will break when the next version arrives? Are you willing to run beta code, or code from the trunk? The answer is different for every application and business.

In the end you need to make a decision from the gut, which means research into the history and pedigree of a dependency might be required.

4. When will it be obsolete?

If you implement the solution to a problem in a mainstream programming language like C#, you can be reasonably sure it will still work and compile in 5 -10 years, even with new versions of the compiler. But, if you implement the same solution with Data Transformation Services (wait, that’s obsolete), or Notification Services (wait, that disappeared), or SQL Distributed Management Objects (oops, it’s only a matter of time) - then you got some rewritin’ to do!

Remember – nothing lasts forever … unless you have all the source code.