Monday, September 29, 2008
Monday, September 29, 2008 6:42:56 PM (SE Asia Standard Time, UTC+07:00) (ASP.Net)

Having been inspired by BlogSvc.Net's recent switch to the ASP.Net MVC framework - I decided that now was as good a time as any to begin to get up-to-speed with ASP.Net MVC

There's a ton of blogosphere content out there already (I've included some of the more helpful links at the bottom of this post). Access to the source at http://www.codeplex.com/aspnet  combined with a healthy dose of Reflector over the System.Web.Routing assembly...  and things are slowly starting to fall into place.

While working through the routing process; how routes are defined, the order they're evaluated and how a default route catchall can be defined... I got a bit hung-up on default routing and in particular the routing of Default.aspx.

When you create a new ASP.Net MVC project (Preview 5).. the project template creates a Default.aspx page and places the following in the code behind...

public void Page_Load(object sender, System.EventArgs e)
{
    HttpContext.Current.RewritePath(Request.ApplicationPath);
    IHttpHandler httpHandler = new MvcHttpHandler();
    httpHandler.ProcessRequest(HttpContext.Current);
}

What's happening here, is that the Url requested (which was Default.aspx) is being re-written to the application root "/" and then transferred off of the Webform HTTP handler and onto the MvcHttpHandler. A request for "/" will match the Default route entry (show further down)... eventually sending us onto one of the route controllers.

In order to really 'grok' what's going on here in just these few lines of code (and the first a lot of people are going to see when they start a new ASP.Net MVC project) ... you need to understand a couple of things. Firstly, there are several different classes named (in part at least) handler, or module in the System.Web.Routing and System.Web.Mvc namespace. It takes a little investigation to work out the responsibility of each of these classes and how they each fit into the complete pipeline, from initial request to the final response and output. For starters - the MvcHttpHandler in the Page_Load event above is not the normal entry point into the framework. (In fact, unless the handler defined for the matching route after the transfer to MvcHttpHandler, is of type MvcHandler - you'll get an exception that says so - also the reason you need to comment out the Page_Load event handling code when you are using Phil's route debugger shown below).

The normal entry point is the UrlRoutingModule  - which you'll see registered in the Web.config. It's this module that inspects incoming requests... checks them for matches against routes in the RouteCollection... and then transfers the request over to the handler that was specified in the matching route entry. One of the best conceptual descriptions of the ASP.Net MVC framework I've come across - was "...think of ASP.Net MVC as a dynamic HttpHandler selector - each route that you define specifies an HttpHandler that will deal with the request from that point on - and that's it". So routes are really nothing more than a way of allowing you to define an HttpHandler that will take care of the rest of the request/response processing for matching Urls.

Back to Default.aspx. The first question I had was why wasn't the default route capturing requests for Default.aspx and why for that matter, was the Page_Load event handling code above required in the first place?

Here's the "Default" route that is created in Global.asax for a new solution...

routes.MapRoute(
    "Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
);

What this route says is... take anything... any Url and treat the first segment as the name of the controller that will be used to handle the request, then take anything as the second segment and use that as the controller method that will be called (controller action) and take anything as the third segment and use that as a parameter for the action method.

Well it turns out there are a couple of reasons that a request for Default.aspx won't work with the route above.

To start .. grab a copy of Phil Haack's Route Debugger. Build the solution and then add a reference to the RouteDebug assembly to your project.

Also remove, or comment out the Page_Load event code in Default.aspx (shown above) since what we want to test here, is what's required to route Default.aspx without the use of any redirection code.

Add Phil's debugger to the Global.asax Application_Start() event handler, as below..

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    RouteTable.Routes.RouteExistingFiles = false;
    RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}

Also note that extra line I've added... for RouteExistingFiles - which is set to false (the default). This is the first reason that Default.aspx won't be routed. The routing module will not (by default) apply routes to Urls that actually exist as files in the web site. This is a good thing... since it allows you to include static content and regular ASP.Net pages in your project. However also be warned that when it is set to RouteExistingFiles = true, all static content (like stylesheets and JavaScript) along with regular ASP.Net pages may no longer be served from the site (if you have a catch-all or default route in place). This might be the desired behavior though.. if you wanted to serve all of your static content from another web server and guarantee that no static content would be coming from your application server.

Try running an ASP.Net MVC app with the code in Application_Start() as above (be sure to have commented out the Page_Load event handling code in Default.aspx first!).

What you should see is that Default.aspx loads as normal and we've not entered the MVC framework.

Now change change RouteExistingFiles to RouteExistingFiles = true and temporarily comment out Phil's route debugger - like this...

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    RouteTable.Routes.RouteExistingFiles = true;
    //RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}

What you should see now is a yellow screen of death (YSOD) "Server Error in '/' Application - The resource cannot be found. HTTP 404 etc..."

The reason we're seeing this is because (again - in the default ASP.Net MVC Solution) there is no controller called Default.aspx. We supplied 'something' in the url and that 'something' was matched as the first segment or token of the Default route (incidentally, naming the route 'Default' has nothing to do with 'Default.aspx' - it's just a name for the route and could be anything). The route actually matched. If you uncomment the RouteDebugger and run it again - you'll see that the Default route was in fact matched to our request. matched_01 However when the framework (the MvcRouteHandler, and then the MvcHandler controller factory in this case) tried to find a controller class called 'Default.aspx' - it couldn't, and so returned an Http 404 file not found error.

The only controllers that exist in the solution - are HomeController, and the AccountController - and our controller token didn't match either of these. You can try changing the Url to "/boo" or "/foo" "/mycontroller" (comment out the RouteDebugger again) and you'll see the same 404 not found error returned again each time. We match the default route, but there are no controllers for any of these controller tokens. However if you try "/home" - bingo... you'll hit the home controller, with a default action of Index and the Index.aspx page in the Views/Home/ directory will be served up as the response (Note: with RouteExistingFiles still set to true... the style sheet won't be served).

Ok so what if we want Default.aspx to route to the home controller? We could add the following route...

routes.MapRoute(
    "Start",                                                // Route name
    "Default.aspx",                                         // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
); 
But this must appear above the Default route since the first route that matches - wins.

With the 'Start' route in place, Default.aspx is now being matched literally - and the default values for the controller, action and id are being used to serve up Views/Home/Index.aspx (again - this will only work if RouteExistingFiles = true while a 'real' Default.aspx page still exists in the root of your site).

As an aside - if we wanted to catch all requests for non-existent controllers you could use a route like this..

routes.MapRoute(
    "Catchall",                                              // Route name
    "{*anything}",                                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
); 

The 'Catchall' route would need to come before the 'Default' route and after any controller specific routes like '/home /news /products... etc - although some might question the wisdom of having such a route. An alternative approach would be to correctly deal with the 404 file not found errors with a custom error page and then take things from there.

So from this little exercise we can now see that there are two things that prevent the routing of Default.aspx. The first - is that the default setting for RouteExistingFiles  is false - and for good reason too. The second, is that even if we were going to route Default.aspx, matching on the Default route still doesn't help since Default.aspx will be matched to the controller token in the route, and there is no such controller in the solution.

So what are the practical options for dealing with Default.aspx? Well I think there are three you can choose from...

  1. Leave Default.aspx unrouted and unredirected as the entry point to your application - with static links that take your users into the MVC portion of the app (or other static content).
  2. Redirect Default.aspx in the code behind, either using the Page_Load event handler code as shown above, or use Response.Redirect("~/home") to send them to the Home controller (although this is a round-trip redirect).
  3. Rename or delete Default.aspx. Despite the warning in the markup that says that default.aspx is required to ensure that ASP.NET MVC is activated by IIS when a user makes a "/" request... it's not actually needed in either the VS dev server, or IIS7. The default request will remain an application root request "/" and will be caught by the default route and sent to the home controller.

 

And that's that. I really don't feel good about doing much until I understand how things start... and how we got to point B from point A. The learning curve in ASP.Net MVC is fairly steep, and started (for me at least) with the humble Default.aspx page.

Here are a few links that helped along the way (and are current for ASP.Net MVC Preview release 5).

http://msmvps.com/blogs/luisabreu/archive/2008/07/04/the-routing-series.aspx
http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx
http://bradwilson.typepad.com/blog/2008/08/partial-renderi.html
http://lostintangent.com/2008/07/03/aspnet-mvc-controlleractioninvoker-part-1/



| Comments [1] | | #  
Tuesday, September 23, 2008
Tuesday, September 23, 2008 4:07:21 PM (SE Asia Standard Time, UTC+07:00) (Security)

I must have been asleep when this 'feature' appeared. I downloaded the latest ASP.Net MVC Storefront sample today from Codeplex. I then dragged the downloaded Zip file over to my project folder, extracted it and built the project. When I tried to run some of the unit tests - I got "Failed to queue test run me@myserver2008-09-23 15:20:05': Test Run deployment issue: The location of the file or directory '\...\.config' is not trusted.

unblockA quick Google and I found the problem. There's a new attribute on files that you download from the Internet. Right click on the Zip file, choose Properties.. and you'll see the window as shown, with a message that says: This file came from another computer and might be blocked to help protect this computer. You need to click the Unblock button before you extract the contents of the Zip file... and then everything works fine...



| Comments [0] | | #  
Monday, September 15, 2008
Monday, September 15, 2008 11:41:43 PM (SE Asia Standard Time, UTC+07:00) (Hardware | Other Tech)

Update: 09 Oct 2008 Well - I needed a decent mouse - and the Logitech G9 Laser mouse totally rocks. Downside.. couldn't work out how to swap the mouse on the Kensington Slimblade set.. since the wireless USB dongle reports both a mouse and keyboard. Luckily I'm warming up to my IBM SpaceSaver II and so that's the combination I'm using now.. SpaceSaver II and Logitech G9. Also received a Luxeed LED keyboard from the manufacturer in Korea.. err.. they were very kind and helpful in getting a keyboard to me,  so it pains me to have to report that there are some quality and manufacturing issues with this keyboard. I was getting phantom carriage returns, and the 'F' and 'G' keys were sticking down, so no go here. A real pity because the keyboard layout is perfect. Illuminated LED keys with separate colors assigned to control keys and letter keys - was the closest I've come to the perfect keyboard setup. Sigh.

Original post...

This is officially part two in my never ending quest for the worlds greatest keyboard. Part one is here - In Search of The World's Best Computer Keyboard

Exif_JPEG_PICTURE                                              My IBM SpaceSaver II arrived today. Not sure what to say. The keyboard layout is perfect; compact but not too small. Everything is in the right place. No number pad which is important for me (as I mentioned in the first post) and helps to keep the keyboard square with the screen. 

Pros: It just works; with no special drivers it works fine in Vista (apart from an active PS2 to USB converter) - the track-point and the middle button scrolling option also works. I'm actually able to use track-point and built in mouse buttons quite well  - which was a little bit surprising (although I will use a dedicated mouse). Looks nice too (the picture doesn't do it justice) and fits well with my black theme.

Cons: Hmm.. key action feels very cheap compared to my ThinkPad T61p keyboard (ThinkPad keyboards are still the greatest keyboards in the world). Key height, action, spacing and travel also aren't nearly as comfortable or as refined as the Kensington Slimblade.

Verdict: Good but disappointing key action - so not the world's greatest keyboard I'm afraid.

 

Kensington_01 Pictured left - my current top of the pile choice - the Kensington Slimblade Media Set. My only recommendation here would be to ditch the mouse. The track-ball is too small for day to day use. Bummer.

Still it's the closest there is at the moment... however...

 

 

Luxceed_01 ...there's another candidate out there - one to check out for sure.. the Luxeed LED Keyboard. This one will be fun try. Amazingly - they've gone for nearly the same keyboard layout as the SpaceSaver II (and not as mad as the Maximus Optimus - which I would also have gone for had they made the number pad detachable) . CTRL-Windows-Alt - are just the way I like them all on their own to the left of the space bar (no pesky function key). If the key action is as smooth or even close to the feel of the ThinkPad or the Kensington - this could be the winner. Will report back in Part III if I manage to get hold of one...  :-)



| Comments [2] | | #  
Tuesday, September 02, 2008
Tuesday, September 02, 2008 2:29:08 PM (SE Asia Standard Time, UTC+07:00) (Hardware | Other Tech)

Awesome - my new 2.5" 320GB Hitachi 7200 rpm 16MB cache Travelstar 7K320 hard disk arrived today. The drive was actually made in Thailand - but I had to order it from Singapore... grrr...

Last March I purchased a ThinkPad T61p. I love this notebook... but I made the mistake of not putting a large enough drive in it at the time.

Here are the steps I took to swapping the drive and resizing the partitions. I had BitLocker installed - with the system volume C: encrypted, so there was a S: partition for boot (as BitLocker creates) and a D: volume where I keep all my data.

  1. Backed up D:
  2. Did a Complete PC Backup to an external USB drive. Control Panel, System Maintenance, Backup and Restore Center, Back up Computer. This creates an image based backup (like Ghost, or TrueImage) including all partition information. NOTE: The image backup process creates a 'decrypted' backup image so you need to keep this drive safe in the case of regular backups.
  3. Swapped the drives.
  4. Booted from my Vista 64 bit OS DVD - and chose "Repair your computer..." (after the date and time options) followed by "Windows Complete PC Restore" - restore completed.. and reboot.
  5. After booting - launched the Computer Management snap-in (right click on My Computer and choose "Manage"). Went into Disk Management - made the C: volume the active volume (in preparation for removing the BitLocker created S: volume)..
  6. Reboot - you'll see an error message that boot files cannot be found
  7. Boot again from Vista OS DVD
  8. Choose "Repair" - and "Startup Repair" (first option) - the boot files and MBR record will be recreated on the C: volume.
  9. Reboot and go back into Disk Management. Delete the S: volume (and in my case the D: data volume too). This will make room for a contiguous extension of the C: volume.
  10. Then in Disk Management right click on the C: volume and choose 'Extent Volume.."  (this was the whole point of the exercise for me - I needed to increase the size of my system volume).
  11. Reboot - system should be fine booting from C:. Re-run the BitLocker drive preparation tool - and re-create the new 1.5GB S: boot volume. And then in my case re-create the D: data volume.
  12. Reboot - all done - restore data to D: and re-encrypt C:.

 

Presto - and it worked flawlessly. Another option would have been to just use the Windows Complete PC Restore - and then use a tool like Acronis Disk Director to reorganize the partitions before re-encrypting the C: volume. However in this case - it all worked without the use of any 3rd party software. I'll give MS credit for their new image based backup system in Vista. I've used it several times now and it's always worked. :-)

Boot times are much improved as well. The previous drive was a Seagate Momentus 7200rpm 160GB disk - but it always felt sluggish to me. The new Hitachi disk is much quicker (although it's reviewed as slightly slower to the new 320GB offerings from both Seagate and Western Digital).



| Comments [0] | | #  
Tuesday, September 02, 2008 12:58:33 PM (SE Asia Standard Time, UTC+07:00) (General)