eWorld.UI - Matt Hawley

Ramblings of Matt

ASP.NET MVC - Legacy Url Routing

April 25, 2008 00:47 by matthaw
In addition to blogging, I'm also using Twitter. Follow me @matthawley

Recently, we've been converting over a lot of our ASP.NET Web Form pages to use ASP.NET MVC. While this is no small feat by itself, the underlying problem of having a new Url structure in the site while still supporting legacy Url's was necessary. The idea, is that you hit a page that no longer exists, and you get redirected to the appropriate controller & action within MVC.



  1. A legacy Url is requested from your site. For example, http://www.server.com/Users/Login.aspx
  2. ASP.NET routing intercepts the request and matches a route from your route collection
  3. Instead of using the MvcRouteHandler, a LegacyRouteHandler is invoked.
  4. Using the LegacyRouteHandler, it'll use the route redirection name you specified, generate the MVC Url, and issue a HTTP 301 with the location of http://www.server.com/site/login.


First, we should define our legacy route class. This is necessary because we need to expose an additional property to enable our routing handler to find the correct MVC route.

   1: // The legacy route class that exposes a RedirectActionName
   2: public class LegacyRoute : Route {
   3:     public LegacyRoute(string url, string redirectActionName, IRouteHandler routeHandler)
   4:         : base(url, routeHandler)
   5:     {
   6:         RedirectActionName = redirectActionName;
   7:     }
   9:     public string RedirectActionName { get; set; }
  10: }

Secondly, we need to define the route handler and associated http handler. The route handler derives from IRouteHandler, and will be the class used when creating your legacy routing. The http handler derives from MvcHandler because it gives us some critical information, like RequestContext. You'll also notice that (while not in the code) you need to copy all of the querystring parameters from the request over. This is a necessary step because the GetVirtualPath method call will take all route data (from RouteData.Values) and try and utilize that when building the Url itself.

   1: // The legacy route handler, used for getting the HttpHandler for the request
   2: public class LegacyRouteHandler : IRouteHandler {
   3:     public IHttpHandler GetHttpHandler(RequestContext requestContext) {
   4:         return new LegacyHandler(requestContext)
   5:     }
   6: }
   8: // The legacy HttpHandler that handles the request
   9: public class LegacyHandler : MvcHandler {
  10:     public LegacyHandler(RequestContext requestContext) : base(requestContext) { }
  12:     protected override void ProcessRequest(HttpContextBase httpContext) {
  13:         string redirectActionName = ((LegacyRoute)RequestContext.RouteData.Route).RedirectActionName;
  15:         // ... copy all of the querystring parameters and put them within RouteContext.RouteData.Values
  17:         VirtualPathData data = RouteTable.Routes.GetVirtualPath(RouteContext, redirectActionName, RouteContext.RouteData.Values);
  19:         httpContext.Status = "301 Moved Permanently";
  20:         httpContext.AppendHeader("Location", data.VirtualPath);
  21:     }
  22: }

Lastly, you need to create your routes within the Global.asax file. Remember, that order is necessary when setting up routing.

   1: public void RegisterRoutes(RouteCollection routes) {
   2:     routes.MapRoute("Login", "site/login", new {
   3:         controller = "Users",
   4:         action = "DisplayLogin"
   5:     });
   7:     routes.Add("", new LegacyRoute(
   8:         "Users/Login.aspx",
   9:         "Login",
  10:         new LegacyRouteHandler()));
  11: }

And that's it. When a request comes in, you'll see the following in Fiddler

  1. A request on "Users/Login.aspx"
  2. A HTTP 301, with a header "Location" and value of "site/login"
  3. A request on "site/login"

Final Thoughts

Granted, there's more you can do with this - like creating your own extension methods like MapRoute and doing better handling of finding the route, but this should get you started. Also, I'm writing the code off the top of my head, so there's no guarantee that any of it works as-is. Please let me know if you have any other thoughts.


Lastly, for those wondering why are we using a HTTP 301 status code? Well read up on them. "301 Moved Permanently" indicates "that all future requests should be directed to the given URI." While your end users will not see any difference other than a new URL in the browser, the 301 status code more aimed towards search engines to update their URL's in their indexes.


Click here to download the source code for this example.


kick it on DotNetKicks.com

New Website Design Live

April 23, 2008 21:46 by matthaw

I've just released my re-designed website and blog. I finally decided it was time to give up the blue and yellow look for a more inviting green and orange look. Below are some notable changes:

  • The major structure and content has stayed the same, but I have made some perks to drive more traffic to portions of the site. All incoming links remain the same.
  • I have also decided to ditch the full width of your browser to a more appealing 1024px width. This gives me the opportunity for placement of information.
  • I've added a quicklinks and a quickblog section to the right sidebar allowing for quick access to the most important information easily.
  • There's no more "ideas" page - mainly because it was just a page that didn't get looked at or where I wouldn't take ideas from anymore.

If you come across any issues, please contact me or leave a comment!

ASP.NET MVC - ActionResult... The Good & Not So Bad

April 18, 2008 18:21 by matthaw

I'm thoroughly enjoying the new ActionResult feature the ASP.NET team introduced into the "refresh" of the Preview 2 bits... or whatever they're calling it now :) Introducing this has increased productivity regarding testing the results of a controller's action. Here's a quick summary of the different types:

  • RenderViewResult - this result is returned when you call RenderView on the controller. It has properties like ViewName, MasterName, ViewEngine, ViewData, and TempData.
  • ActionRedirectResult - this result is returned when you call RedirectToAction on the controller. It exposes a dictionary of information.
  • HttpRedirectResult - this result is returned when you call Redirect. It exposes the Url that it is to redirect to.
  • EmptyResult - This is used for any other purpose in which you don't want the controller to complete the execution on. I have already used this for outputting HTML to the response stream for a specific purpose.

Now that these action results are present, there's no need to have your own view engine that captures the same information. What else is great, is that you can implement your own action result to do whatever you wish!

Onto the Not so Bad

1. When calling RenderView with the parameterless method, this renders testability useless because the RenderViewResult that is returned has Null values where you'd expect to find a value. For example, the critical property "ViewName" is null.

2. I feel that ActionRedirectResult should expose a ViewName and ViewData property. As it stands, when you call RedirectToAction, it takes your view name you've specified and puts it within the RouteValueDictionary it exposes. While this is still testable - it would be much easier to test if I could just go off of ViewName and ViewData.

All in all, this is a very good start to using ActionResults, and I love using them. Hope my insight helps!

kick it on DotNetKicks.com

New Drop of ASP.NET MVC

April 16, 2008 19:27 by matthaw

There's a new drop of ASP.NET MVC out on CodePlex now. Once again, there's some breaking changes, but nothing too terribly bad this time. Notably

  1. Action methods now return an ActionResult.
  2. It contains the changes to routing that Phil Haack mentioned.

Also, they've included all of the test cases too, so now you can be assured they are following good practices! Check out the Read Me for the latest changes in this drop.

Microsoft MVPs: Meet the CodePlex Team

April 15, 2008 20:27 by matthaw

Sara, just posted about the CodePlex team will be at the Sheraton Hotel Seattle for the Communities Side Session this Thursday (4.17.08) afternoon. If you're a Microsoft MVP who uses CodePlex, swing by for some swag, demos, and give us your feedback! Even though I'm fairly new to the team I'm going to take this opportunity to meet a lot of you and just plain listen. If you run into me, don't expect me to answer your questions - but I'll sure listen to your gripes!

Actions: E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Less Ramp-Up Time with Pair Programming?

April 15, 2008 00:50 by matthaw

Being my first day on the CodePlex team, I figured it'd be filled with "read this", "watch that", and "understand da code". Being that any other job I've started in the past has a significant ramp-up time, I would have guessed CodePlex would be no different. While I think there will be some time before I'm fully comfortable, I'd say that coming on board the first day and already writing some code is a success! Thanks pair programming!

New Beta of Website Live

April 13, 2008 01:23 by matthaw

Beta ScreenshotJust wanted to share the beta of my re-designed website. Please kick the tires some for me and give me some honest criticism. The first thing you'll notice is the ditching of the yellow & blue with a more fun green & orange.

You'll also notice that I did a bit of re-organization within the menu, but for the most common tasks - I added a "quicklinks" section that hopefully has the most common tasks one is looking for. I've also tied my blog in with the new design, displaying the last 10 entries, hoping to drive more visitors to the site.

Again, please enjoy this and comment on what you like, don't like, whatever!

Linky to Beta

MVC UI Validation Framework

April 2, 2008 16:36 by matthaw

The last few days I've been working on a MVC UI validation framework that closely follows that of ASP.NET. While my implementation is only in it's baby steps, I decided I would post it to the MVC Contrib project, and follow as much standards as what they have. Overall, the framework is very simple and straight forward in it's use.

Here were my requirements:

  1. It must closely match the ASP.NET UI validation framework.
  2. It should support all of the ASP.NET validators in their minimalistic implementation.
  3. It should require very minimal script inclusion or code to make this happen.
  4. It should only by a client side framework as you should be protecting your data on the server side through normal validations.

What I came up with, includes:

  1. It utilizes the ASP.NET UI validation framework by leveraging the WebUIValidation.js file (emitted through WebResource.axd).
  2. It supports all of the validators, with extended support for validation groups:
    1. RequiredValidator
    2. RegularExpressionValidator
    3. RangeValidator (currently does not support Date or Currency data types)
    4. CompareValidator
    5. CustomValidator
  3. It exposes 2 script inclusion calls, and 1 form validation setup call
    1. ValidatorRegistrationScripts() - Will render the WebUIValidation.js script, and scripts for form validation upon submission. This should be placed within the head tag.
    2. ValidatorInitializationScripts() - Will render all of the "expando" attributes for all validators upon the page and any initialization calls to set things up. This should be placed at the very end of your page before the closing body tag.
    3. FormValidation() - Will return an IDictionary object containing the "onsubmit" attribute. This should be called when creating your <form> tag.
  4. It is only a client-side framework leveraging ASP.NET's WebUIValidation.js

Since you now have an idea of the requirements and what was met, here's an example of it in action:

   1:  <%@ Import Namespace="MvcContrib.UI.Html" %>
   2:  <html>
   3:     <head>
   4:        <%= Html.Form().ValidatorRegistrationScripts() %>
   5:     </head>
   6:     <body>
   7:        <% using(Html.Form<MyController>(c => c.Save(), FormMethod.Post, Html.Form().FormValidation())) { %>
   8:        First Name: <%= Html.TextBox("firstName") %>
   9:        <%= Html.Form().RequiredValidator("firstNameRequired", "firstName", "First Name is Required.") %>
  10:        <%= Html.Form().RegularExpressionValidator("firstNameRegex", "firstName", "[a-zA-Z]*", "First Name can only contain letters.") %>
  11:        <br />
  12:        Age: <%= Html.TextBox("age") %>
  13:        <%= Html.Form().RequiredValidator("ageRequired", "age", "Age is Required.") %>
  14:        <%= Html.Form().RegularExpressionValidator("ageRegex", "age", "[0-9]*", "Age can only be numeric.") %>
  15:        <%= Html.Form().RangeValidator("ageRange", "age", "18", "35", ValidationDataType.Integer, "Age is not in target range of 18-35.") %>
  16:        <br />
  17:        <%= Html.SubmitButton("submit", "Save") %>
  18:        <% } %>
  19:        <%= Html.Form().ValidatorInitializationScripts() %>
  20:     </body>
  21:  </html>

At this point, I've posted it as a patch to MVC Contrib project, and hoping they apply it Smile. If you would like to get your hands on it, check out the patches page, mine is #1063. Please let me know your input, I tried making it as simple as possible to use, and I believe I've achieved that.

Update: My patch has been applied by Eric :) Get the latest build (>= it if you'd like to check this out. Also, let me know of any issues you may find by logging bugs on the CodePlex site.

New Design is Live!

April 1, 2008 01:05 by matthaw

Ahh, I've worked so hard on this. I hope you all enjoy it. Check out my new design as I've taken a different direction for the site. I hope your a real developer and feel at home with my new layout!

Update: Okay, thanks for humoring me :) The site has been officially moved and the link above has been updated.

Categories: General | Personal
Actions: E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Copyright © 2000 - 2024 , Excentrics World