Routing Regression With Two Consecutive Optional Url Parameters–(source Phil Haack)

 

It pains me to say it, but ASP.NET MVC 3 introduces an ugly regression in routing from ASP.NET MVC 2. The good news is that there’s an easy workaround.

The bug manifests when you have a route with two consecutive optional URL parameters and you attempt to use the route to generate an URL. The incoming request matching behavior is unchanged and continues to work fine.

For example, suppose you have the following route defined:

routes.MapRoute("by-day",          "archive/{month}/{day}",        
 new { controller = "Home", action = "Index",           
   month = UrlParameter.Optional, day = UrlParameter.Optional } );<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Notice that the month and day parameters are both optional.

routes.MapRoute("by-day",          
    "archive/{month}/{day}",
    new { controller = "Home", action = "Index", 
    month = UrlParameter.Optional, day = UrlParameter.Optional } );<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Now suppose you have the following view code to generate URLs using this route.

@Url.RouteUrl("by-day", new { month = 1, day = 23 })
@Url.RouteUrl("by-day", new { month = 1 })
@Url.RouteUrl("by-day", null)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

In ASP.NET MVC 2 the above code (well actually, the equivalent to the above code since Razor didn’t exist in ASP.NET MVC 2) would result in the following URLs as you would expect:

  • /archive/1/23
  • /archive/1
  • /archive
  • But in ASP.NET MVC 3, you get:

  • /archive/1/23
  • /archive/1
  •  

  • In the last case, the value returned is null because of this bug. The bug occurs when two or more consecutive optional URL parameters don’t have values specified for URL generation.

    Let’s look at the workaround first, then we’ll dive deeper into why this bug occurs.

    The Workaround

    The workaround is simple. To fix this issue, change the existing route to not have any optional parameters by removing the default values for month and day. This route now handles the first URL where month and day was specified.

    We then add a new route for the other two cases, but this route only has one optional month parameter.

    Here are the two routes after we’re done with these changes.

    routes.MapRoute("by-day",         
     "archive/{month}/{day}",        
     new { controller = "Home", action = "Index"} ); 
     routes.MapRoute("by-month",          "archive/{month}",       
      new { controller = "Home", action = "Index",      
            month = UrlParameter.Optional} );<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    And now, we need to change the last two calls to generate URLs to use the by-month route.

    @Url.RouteUrl("by-day", new { month = 1, day = 23 }) @Url.RouteUrl("by-month", new { month = 1 }) @Url.RouteUrl("by-month", null)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    Just to be clear, this bug affects all the URL generation methods in ASP.NET MVC. So if you were generating action links like so:

    @Html.ActionLink("sample", "Index", "Home", new { month = 1, day = 23 }, null) 
    @Html.ActionLink("sample", "Index", "Home", new { month = 1}, null)
     @Html.ActionLink("sample", "Index", "Home")<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    The last one would be broken without the workaround just provided.

    The workaround is not too bad if you happen to follow the practice of centralizing your URL generation. For example, the developers building http://forums.asp.net/ ran into this problem as well during the upgrade to ASP.NET MVC 3. But rather than having calls to

    ActionLink all over their views, they have calls to methods that are specific to their

    application domain such as ForumDetailUrl. This allowed them to workaround this issue by updating a single method.

    The Root Cause

    For the insanely curious, let’s look at the root cause of this bug. Going back to the original route defined at the top of this post, we never tried generating an URL where only the second optional parameter was specified.

    @Url.RouteUrl("by-day", new { day = 23 })<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    This call really should fail because we didn’t specify a value for the first optional parameter, month. If it’s not clear why it should fail, suppose we allowed this to succeed, what URL would it generate? /archive/23?  Well that’s obviously not correct because when a request is made for that URL, 23 will be interpreted to be the month, not the date.

    In ASP.NET MVC 2, if you made this call, you ended up

    with /archive/System.Web.Mvc.UrlParameter/23. UrlParameter.Optional

    is a class introduced by ASP.NET MVC 2 which ships on its own schedule outside of the core ASP.NET Framework. What that means is we added this new class which provided this new behavior in ASP.NET MVC, but core routing didn’t know about it.

    The way we fixed this in ASP.NET MVC 3 was to make the ToString method of UrlParameter.Optional return an empty string. That solved this bug, but uncovered a bug in core routing where a route with optional parameters having default values behaves incorrectly when two of them don’t have values specified during URL generation. Sound familiar?

    In hindsight, I think it was a mistake to take this fix because it caused a regression for many applications that had worked around the bug. The bug was found very late in our ship cycle and this is just one of the many challenging decisions we make when building software that sometimes don’t work out the way you hoped or expected. All we can do is learn from it and let the experience factor into the next time we are faced with such a dilemma.

    The good news is we have bugs logged against this behavior in core ASP.NET Routing so hopefully this will all get resolved in the next core .NET framework release.

    @helper / razor

     

    Here’s a neat little trick.

    Declare a helper function in your view with the @helper syntax

    image

    Note: you will get intellisense when you type the @ listing the GiveHigh5 function.

     

    Here’s the output:

    image

    enjoyThumbs up

    DRY out your MVC Views

     

    For those of you that don’t know DRY : Don’t Repeat Yourself.

    Take the following two screens

    image

    image

     

    They are nearly identical.

    MVC3/Razor provides two ways to make things DRYer.

    1) Layouts (same as asp.net master pages, not covered in this post)

    2) Partial views

    Here’s how.

    image

    Shows the create page, using the Html.Partial extension method

     

    image

    Shows the edit page, using the Html.Partial extension method on the same partial view as well

     

    image

    Shows the partial view that gets injected (for want of a better word) into the two views shown previously.

    Razor…

    Unless you’ve been living under a rock lately, you’ll at least have heard of MVC and Razor even if you don’t really know what they are…

    ASP.NET MVC 3 is the next major release of ASP.NET MVC. ASP.NET MVC is a part of the ASP.NET Web application framework. It is one of the two different programming models you can use to create ASP.NET Web applications,the other being ASP.NET Web Forms.

    • Complete control over your HTML Markup
    • Enables rich AJAX and jQuery integration
    • Allows you to create SEO-friendly URLs for your site
    • Makes Test Driven Development (TDD) easy

    I personally like working with MVC, can’t exactly put my finger on why, but most of my web-based projects these days are MVC2/3.

    Now: Microsoft have really been pushing out new technologies over the last number of years and a few month back I found myself saying that I would try not to dip my fingers in all the new pies as I’ve been doing over the last number of years. One of these so called pies was the Razor view engine.

    However I happened to have a play around with a few Webmatrix projects and was exposed to Razor unintentionally……

    All I can say is i’m very impressed and hence forth all my new MVC3 projects will be Razor based.

    The best thing is that there is no learning curve (nearly), bacially just put a “@” character before any code and a way you go! Yes it’s really that simple.

    e.g.

     

    image

    Shows the controller setting a value on the ViewBag (a dynamic property .net 4)

     

    image

    Shows the client page rendering the text, also shows the new Layout mechanism for Razor.

     

    image

    Important part is the @RenderBody()  ; note the DOCTYPE too HTML5…Secret telling smile

    Retrieving stock quotes in C#

     

    I’ve been playing with the idea of writing an currency converter for my new Windows Phone 7. Stumbled across a tweet by Scot Hanselman regarding http://channel9.msdn.com/coding4fun. Turns out someone else beat me to it with the BING “api” (more of a url naming convention really).

    So I got a little diverted and write a little app to get stock quotes from google.

    Here’s how enjoy…(Using a default ticker of vodafone (.net 4.0)

    image

    WP7

     

    Received my first windows phone today, just had to try an app after finishing work..
    Wrote a little app to search the subnet looking for some jboss application servers, works just as well on my phone connected to wifi Rolling on the floor laughing

    Pretty chuffed with myself, the effort over the last number of years learning xaml has really paid off..

    it’s helped in

    * WF4 Activity designers
    * WPF application
    * Silverlight web
    And now…. Mobile phone apps

    image

    Get the windows phone sdk here

    http://www.microsoft.com/downloads/en/details.aspx?FamilyID=04704acf-a63a-4f97-952c-8b51b34b00ce

    VS2010 Script chooser

    In a visual studio 2010 asp page, start typing

    <script src=

    You’ll get presented with the following screen.

    image

     

    Choose the Pick URL … option and you’ll be presented with the following screen

    image

    I think you’ll figure out the rest Ninja

    SQL Server 2008 Calculation Variables

    SqlServer 2008 has a short hand/compact way of assigning calculation variables.

     

    e.g

    Sql2005

    DECLARE @val    INT
    set @val = 1;
    set @val = @val + 1;

     

    Sql2008

    DECLARE @val    INT
    set @val = 1;
    set @val += 1;

     

    Same applies to other operators,  *=, –=, /=