Upcoming Changes In Routing

asp.net, asp.net mvc, code 0 comments suggest edit

Made a few corrections on having default.aspx in the root due to a minor bug we just found. Isn’t preview code so much fun?

We’ve been making some changes to routing to make it more powerful and useful. But as Uncle Ben says, with more power comes more responsibility. I’ll list out the changes first and then discuss some of the implication of the changes.

  • Routes no longer treat the . character as a separator. Currently, routes treat the . and / characters as special. They are separator characters. The upcoming release of routing will only treat the / as separator.
  • Routes may have multiple (non-adjacent) url parameters in a segment. Currently, URL parameters in a route must fill up the space between separators. For example, {param1}/{param2}.{param3}. With the upcoming release, a URL segment may have more than one parameter as long as they are separated by a literal. For example {param1}.{ext}/{param3}-{param4}is now valid.

Passing Parameter Values With Dots

With this change, the dot character becomes just another literal. It’s no longer “special”. What does this buy us? Suppose you are building a site that can present information about other sites. For example, you might want to support URLs like this:

  • http://site-info.example.com/site/www.haacked.com/rss
  • http://site-info.example.com/site/www.haacked.com/stats

That first URL will display information about the RSS feed at www.haacked.com while the second one will show general site stats. To make this happen, you might define a route like this.

routes.Add(new Route("site/{domain}/{action}" 
  , new MvcRouteHandler()) 
{ 
  Defaults=new RouteValueDictionary(new {controller="site"})  
});

Which routes to the following controller and action method.

public class SiteController : Controller
{
  public void Rss(string domain)
  {
    RssData rss = GetRssData(domain);
    RenderView("Rss", rss);
  }

  public void Stats(string domain)
  {
    SiteStatistics stats = GetSiteStatistics(domain);
    RenderView("Stats", stats);
  }
}

The basic idea here is that the domain (such as www.haacked.com in the example URLs above) would get passed to the domain parameter of the action methods. The only problem is, it does not work with the previous routing system because routing considered the dot character in the URL as a separator. You would have had to define routes with URLs like site/{sub}.{domain}.{toplevel}/{action} but then that doesn’t work for URLs with two sub-domains or no sub-domain.

Since we no longer treat the dot as special, this scenario is now possible.

Multiple URL Segments

What does adding multiple URL segments buy us? Well it continues to allow using routing with URLs that do have extensions. For example, suppose you want to route a request to the following action method: public void List(string category, string format)

With both the previous and new routing, you can match the request for the URL…

/products/list/beverages.xml

with the route

{controller}/{action}/{category}.{format}

To call that action method. But suppose you don’t want to use file extensions, but still want to specify the format in a special way. With the new routing, you can use any character as a separator. For example, maybe you want to use the dash character to separate the category from the format. You could then match the URL

/products/list/beverages-xml

with the route

{controller}/{action}/{category}-{format}

and still call that action method.

Note that we now allow any character (allowed in the URL and that is not a dash) to pretty much be a separator. So if you really wanted to, though not sure why you would, you could use BLAH to separate out the format. Thus you could match the route

/products/list/beveragesBLAHxml

with the route

{controller}/{action}/{category}BLAH{format}

and it would still route to the same List method above.

Consequences

This makes routing more powerful, but there are consequences to be aware of.

For example, using the default routes as defined in the ASP.NET MVC Preview 2 project template, a request for “/Default.aspx” fails because it can’t find a controller with the name “Default.aspx”. Huh? Well “/Default.aspx” now matches the route {controller}/{action}/{id} (because of the defaults for {id} and {action}) because we don’t treat the dot as special.

Not only that, what about a request for /images/jpegs/foo.jpg? Wouldn’t routing try to route that to controller=”images”, action=”jpegs”, id=”foo.jpg” now?

The decision we made in this case was that by default, routing should not apply to files on disk. That is, routing now checks to see if the file is on disk before attempting to route (via the Virtual Path Provider).

If the file is on disk, we pop out and don’t route and let the web server handle the request normally. If the file doesn’t exist, we attempt to apply routing. This makes sure we don’t screw around with requests for static resources on disk. Of course, this default can be changed by setting the property RouteTable.Routes.RouteExistingFiles to be true.

Why Blog This Now?

The Dynamic Data team is scooping the MVC team on these routing changes. ;) They are releasing a preview of their latest changes to Dynamic Data which includes using our new routing dll.

Check out ScottGu’s post on the subject. I really feel Dynamic Data is the most underrated new technology coming out from the ASP.NET team. When you dig into it, it is really cool. The “scaffolding” part is only the tip of the iceberg.

Installing the Dynamic Data preview requires installing the routing assembly into the GAC. If you install this, it may break existing MVC Preview 2 sitesbecause the assembly loader favors the GAC when the assembly is the same version. And the routing assembly is the same version as the one in Preview 2.

The Dynamic Data Preview readme has the steps to update your MVC Preview 2 project to work with the new routing. You’ll notice that the readme recommends having a Default.aspx file in the root which redirects to /Home. Technically, the Default.aspx file in the root won’t be necessary in the final release because of the suggested routing changes (it is necessary now due to a minor bug). Unfortunately, Cassini doesn’t work correctly when you make a request for “/” and there is no default document. It doesn’t run any of the ASP.NET Modules. So we kept the file in the root, but you can will be able to remove it when deploying to IIS 7. So to recap this last point, the Default.aspx in the project root is to make sure that pre-SP1 Cassini works correctly as well as IIS 6 without star mapping. It’s will not be needed for IIS 7 in the future, but is needed for the time being.

We will have a new CodePlex source code push in a couple of weeks with an updated version of MVC that supports the new routing engine.

Technorati Tags: ASP.NET MVC,ASP.NET,Routing,Dynamic Data

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

36 responses

  1. Avatar for Kurt
    Kurt April 10th, 2008

    Sweet.
    Given the "any character as a route separator" thing, are you considering making greedy parameters possible in the future? I might want something like this, for instance:
    /{path*}.{extension}
    Also, why's the / still considered a separator, rather than treating it like the .?

  2. Avatar for Kurt
    Kurt April 10th, 2008

    Whoops, to clarify. I meant "greedy parameters that aren't the last in the route". :)

  3. Avatar for Chad Lee
    Chad Lee April 10th, 2008

    Any chance of supporting Monorail-style areas in the next release?

  4. Avatar for Nick Berardi
    Nick Berardi April 10th, 2008

    Phil,
    >>So if you really wanted to, though not sure why you would, you >>could use BLAH to separate out the format. Thus you could match >>the route
    >>/products/list/beveragesBLAHxml
    >>with the route
    >>{controller}/{action}/{category}BLAH{format}
    This is very wonderful news, and I have already found a use for it: On my current project http://www.ideapipe.com, I have to define pages like this:
    http://www.ideapipe.com/pag...
    However now with this new routing I can do the following, which I like the format much better:
    http://www.ideapipe.com/page2
    By using, as one of my routes:
    /page{page}

  5. Avatar for Haacked
    Haacked April 10th, 2008

    @kurt our parameters are greedy by deafult.

  6. Avatar for Kurt
    Kurt April 10th, 2008

    @Haacked They suck down forward slashes too? Maybe I should have used the word "wildcard" rather than greedy. Last I read about those, they only worked as the last parameter in a route.

  7. Avatar for spootwo
    spootwo April 10th, 2008

    I would also like to know if /{path*}.{extension} will work in the future. The old guard at work is cautious moving to a new page model and they require the reassurance that they can still add web pages the old way before I can proceed to bring mvc goodness to my workplace.

  8. Avatar for Haacked
    Haacked April 10th, 2008

    @Kurt, no they don't suck down slashes. Only catch-all parametres suck down slashes. The catch-all parameter must be at the end.
    @spoottwo it should be /{path}.{extension}
    No need for a *.
    I think I'll try to write up a new blog post with a better explanation of routing. :)

  9. Avatar for VIjay Santhanam
    VIjay Santhanam April 10th, 2008

    URL generation is really important to me, and being able to define routes with customised string formatting would be useful.
    right now i think spaces are left as is -- and firefox seems to deal okay.
    So for,
    /Clients/Search/{query} with controller="Clients",action="Search"
    public void Search(string query){/*action*/}
    and,
    ActionLink<ClientsController>(c=> c.Search("John Citiz"), "Search")
    returns "/Clients/Search/john citi".
    is that url legal? If it is that's great! but otherwise it'd be nice to customize how query gets formatted. Maybe a static char replacement dictionary.



  10. Avatar for VIjay Santhanam
    VIjay Santhanam April 10th, 2008

    Nope, sorry, spaces are clearly url encoded. firefox just displays them as spaces in the status bar.
    It'd be great to change that space to /clients/search/john_citizen.
    Oh so neat!

  11. Avatar for tgmdbm
    tgmdbm April 10th, 2008

    @Vijay, yeah spaces are encoded to "%20", i think perhaps they should be encoded to "+" which is what happens when you submit a form with method="get"
    That would also mean that routing should decode "+" to space! As it stands at the moment, you would end up with a + in the RouteData.
    @Haacked, lovin these changes! Keep it up.

  12. Avatar for Chris Cavanagh
    Chris Cavanagh April 10th, 2008

    I'm glad you've added the "RouteTable.Routes.RouteExistingFiles" flag. Please don't remove it! I'm already dependent on every request being routable :o)

  13. Avatar for Gokhan Demir
    Gokhan Demir April 10th, 2008

    phil please also consider to not treat '/' char as separator. i think routing be more nifty with supporting archive.org like urls (e.g. http://web.archive.org/web/...

  14. Avatar for Gokhan Demir
    Gokhan Demir April 10th, 2008

    phil please also consider to not treat / char as separator. i think routing be more nifty with supporting archive.org like urls (e.g. http://web.archive.org/web/...

  15. Avatar for Kurt
    Kurt April 10th, 2008

    You can do an archive style url like this, I think:
    "web/*/{url*}"

  16. Avatar for Chance
    Chance April 10th, 2008

    Phil,
    Have you guys considered expanding the routing to encompass full URL routing? For example: "{controller}.[domain]/{action}/{id}" which would be great for instances such as forums.website.com or manage.website.com/page/edi...
    I realize that I could write my own custom routing for this - (which I plan to do) but I think it could potentially benefit the routing system as a whole.

  17. Avatar for Haacked
    Haacked April 10th, 2008

    @Chance Right now we haven't looked at that only because by the time you've hit our code, the domain has already been determined.
    Our code runs at the application level and this could be confusing when you have a route for a domain, but IIS host headers points it elsewhere.
    However, I do know some people map multiple host headers to the same website (Subtext does this!) so it wouldn't be hard to write a custom route that does this. If this ends up being a real important scenario for lots of people, I'm sure we'll consider it. :)

  18. Avatar for davidalpert
    davidalpert April 10th, 2008

    Does this mean that i can suddenly drop MVC-style code (using the System.Web.Routing mechanism) into an existing Web Forms application and the routing mechanism will default to Web Forms for files that exist on disk such as my existing pages, yet route into my models and controllers if a URL would otherwise return a 404?

  19. Avatar for Chance
    Chance April 11th, 2008

    Thanks Phil, I figured that may have a showstopper for the idea but I had to throw it out there anyways. Subdomain seperation is clean and elegant in a lot of scenarios. I imagine though that if someone is willing to go through the hassle of getting the routing setup on the server, they certainly wouldn't mind writing a few lines of code to handle it in the code.

  20. Avatar for Dragan Panjkov
    Dragan Panjkov April 13th, 2008

    What if I download dynamic data preview and manually copy routing dll into bin folder of my mvc sites? It should use that version (I don't plan to install dynamic data preview, so then routing dll will not be deployed into gac)

  21. Avatar for Scott
    Scott April 30th, 2008

    Is there a way to tell routes to ignore period as a separator in a certain part of a url? For instance:
    http://whatever.com/files/{filename}
    method: DELETE
    to delete a certain file?

  22. Avatar for Dirk
    Dirk May 5th, 2008

    I'm trying to migrate a "legacy" ASP.NET webforms site bit-by-bit (not literally :/)and some of my "old" webforms pages use ASP.NET Ajax. It seems that certain routes interfere with the /WebResource.axd and /ScriptResource.axd handlers. This leads to Ajax not working correctly or failing completely. I wonder if these handlers could be removed from routing by default, just like the static files.

  23. Avatar for mario
    mario June 4th, 2008

    why not regex? it would make the default mvc so much more flexible. more importantly, we don't have to worry about 'valid' cahracers. only developers will be using MVC and if they can't figure out regular expressions they should seek another profession. maybe use named captures
    (?<controller>[^/]+)/(?<action>[^/]+)/(?<id>.*)

  24. Avatar for Rodj
    Rodj June 17th, 2008

    I have an issue with an url encoded '/' (ie. %2f) being parsed by the routing engine.
    for example: simple route "{category}/{product}" however a category is "3/5 door" so after encoding the url returns "3%2f5+door/a-product". The request goes through as "3/5 door/a-product" which will not match any routes.
    Is there a way round this?

  25. Avatar for haacked
    haacked June 22nd, 2008

    @Rodj unfortunately, by the time ASP.NET Hands us the route, that %2f is already unencoded. So there's no way for us to see that as anything other than a "/".
    The only workaround I can think of is to replace "/" within categories with a well-known character when generating URLs and after matching URLs. I'll dig into this some more to find out if there are other options.

  26. Avatar for Eilon Lipton
    Eilon Lipton June 22nd, 2008

    @Rodj As Phil says you'll have to encode the "/" as some other character because it has a very special meaning.
    @mario The problem with regexes is right there in your comment. Even the regex you've shown is not equivalent to the default MVC route. You have to wrap additional "(regex)?" clauses around each segment to make them optional. That makes the regex one huge cluster of punctuation that is incredibly hard for people to read.
    I'd like to meet these "developers" who think they understand regexes. Most of the ones I know *think* they understand regexes but end up creating bad regexes that don't entirely solve the original problem.

  27. Avatar for Mladen Mihajlovic
    Mladen Mihajlovic September 8th, 2008

    I'm having the same problem as Rodj (I have a / and \ in a category name) There should really be a way to do this properly. How about adding in some property (or character) to tell it it will have a certain number of slashes. Also shouldn't the {id*} thing work?
    There must be a way to do this - / and \ characters should be usable if they're url encoded at least.

  28. Avatar for Mohan
    Mohan September 23rd, 2008

    Am having the same problem as "Dirk", certain routes interfere with the /WebResource.axd and /ScriptResource.axd handlers. This leads to Ajax not working correctly or failing completely. Is thre any solution for the same...?
    Even i tried with Routes.IgnoreRoute option also, but no use.

  29. Avatar for Mohan
    Mohan September 25th, 2008

    Mistake from my side, forgot to put "IgnoreRoute" on top before adding routes.

  30. Avatar for borisCallens
    borisCallens March 22nd, 2009

    What I found on blogs, SO and a bit here is that currently there seems to be no way around the "/" thing. @Phil: Are there any plans of solving this issue in the future?

  31. Avatar for James Radford, Web Developer
    James Radford, Web Developer June 7th, 2010

    so what version of MVC is this in? ASP.NET MVC 2?
    Thanks,

  32. Avatar for Peter Morris
    Peter Morris November 15th, 2010

    I am having problems with this routing. The {code} I am using is entered by the customer, this code must be exactly as the customer enters it because it maps to a back-end customer server.
    So when the customer has a code with a forward slash in it such as N/A I end up with
    http://myserver/mycontroller/action/N/A
    A forward slash in a customer code is not unusual, the same for other codes such as "A:B" which results in a "Bad request".
    I use ActionLink extensively throughout this Intranet application because it saves me so much time by picking up values from previous requests. e.g. going from mycontroller/index/a/b/c/d/e to details will fill in the a/b/c/d/e for me automatically from the routes, so I don't want to have to resort to creating the urls myself.
    What about allowing something like this?
    routes.MapRoute("", "{controller}/{action}/{code}",
    new { "code" = UrlParameter.Optional + UrlParameter.Base64Encode});

  33. Avatar for haacked
    haacked November 17th, 2010

    @Peter, you can use a catch-all parameter for the customer code.
    "{controller}/{action}/{*code}"

  34. Avatar for Peter Morris
    Peter Morris November 19th, 2010

    I've managed to get ASP MVC encoding special characters before the query string using an exclamation mark. So the route {controller}/{action}/{code} when presented with the code "N/A" will use "N!2fA"
    Here's how I did it:
    blog.peterlesliemorris.com/...

  35. Avatar for Peter Morris
    Peter Morris November 19th, 2010

    @phil : Catch all was no use, because some of my routes look like this
    {controller}/{action}/{make}/{model}
    Where both make and model may contain any number of weird characters, that's why I had to go with the approach above.

  36. Avatar for Tridip
    Tridip September 12th, 2011

    regarding Upcoming Changes In Routing will be possible from which version. i am working with asp.net 4.0 but when i try to implement the routing pattern like
    1) {param1}.{ext}/{param3}-{param4}
    2) /{path*}.{extension}
    but i got error. page not found. so please guide me from which version i we can use the above pattern. thanks