5,550,131 members and growing! (19,186 online)
Email Password   helpLost your password?
Web Development » ASP.NET » General     Intermediate License: The Code Project Open License (CPOL)

ASP.NET MVC Part 2

By Mark Nischalke

A continued look at ASP.NET MVC and URL Routing
C# (C# 3.0, C#), .NET (.NET, .NET 3.5), ASP.NET

Posted: 5 Jul 2008
Updated: 5 Jul 2008
Views: 4,806
Bookmarked: 11 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
votes for this Article.
Popularity: 0.00 Rating: 0.00 out of 5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Introduction

This is article is a continuation of ASP.NET MVC Part 1 in which I will attempt to elaborate on the URL Routing portion of MVC. A word of caution though, the previous article was based on the Preview 2 release of ASP.NET MVC, however, this article uses the Preview 3 release. There are some things that have changed between these releases.

Prerequisites

ASP.NET 3.5 Extensions
ASP.NET MVC Preview 3 http://www.asp.net/downloads/3.5-extensions/
MusicCatalog database, included in download

Review of Model-View-Controller Pattern

To recap a little from the first part of this article, let’s briefly discuss the MVC pattern. MVC is a pattern that divides an application into separate areas of responsibility; Model, View and Controller.

Model: Models are responsible for maintaining the state of the application, often by using a database.
View: These components are strictly for displaying data, they provide no functionality beyond formatting it for display.
Controller: Controllers are the central communication mechanism in a MVC application. They communicate actions from the views to the model and back.

One of the main points with the MVC pattern is that there is no direct communication between the model and view. The point of this article is to detail how this communication takes place using URL Routing.

URL Routing

URL Routing is a part the MVC Web Application framework. However, Microsoft has chosen to also make it a part of the .Net Framework 3.5 Service Pack 1 though its functionality may of course differ from the implementation in MVC. We can take a look at how URL Routing is accomplished in MVC to get an understanding of how it may be implements in non-MVC web applications. The URL Routing components are, appropriately enough, in the System.Web.Routing namespace. The images below show the two version of each namespace.


URL Routing makes use of HTTPHandlers and HTTPModules as described below to perform the desired actions as we will see

Routing HTTPModule

As brief review, HTTPModules are inserted into the ASP.NET pipeline as a means to preprocess requests. HTTPModules for authentication, session state, and others are already built in to ASP.NET applications. Since these modules process requests before they get to the page level, it is a perfect solution for URL Routing; requests are handled by the module, and then routed appropriately. Adding the below entry into the web.config file, under the httpModules element places the UrlRoutingModule into the pipeline for a web application.

<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, 
System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 

As a word of caution, Preview 3 uses the follow to avoid conflicts with SP1

<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, 
System.Web.Routing, Version=0.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

The module registers two event handlers, OnApplicationPostMapRequestHandler, OnApplicationPostMapRequestHandler in its Init method. The latter is, of course, used to handle the PostMapRequestHandler event on the HttpApplication object. This event is triggered after the ASP.NET engine has mapped a request to an even t handler. The former is used to handler the PostResolveRequestCache event which occurs when the ASP.NET engine bypasses and event handler to serve a request from cache. In the handler for this event is where we can see the magic for URL Routing occuring. Here a new RequestData object is created and added to the HttpContext collection. An instance of an IRouteHandler interface is added and HttpContext.RewritePath is used to redirect the request to UrlRouting.axd

RequestData data2 = new RequestData();
data2.OriginalPath = context.Request.Path;
data2.HttpHandler = httpHandler;
context.Items[_requestDataKey] = data2;
context.RewritePath("~/UrlRouting.axd"); 

Routing HttpHandler

Again, as a brief review, HttpHandlers are used simply to take requests and processes them. In the case of URL Routing, the UrlRoutingHandler will used to process requests coming from UrlRouting.axd that we saw above. In the ProcessRequest method of the UrlRoutingHandler an instance of the controller for the request being processed is attempted to be created from an IControllerFactory instance and then the Execute method called.
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
// Instantiate the controller and call Execute
IControllerFactory factory = ControllerBuilder.GetControllerFactory();
IController controller = factory.CreateController(RequestContext, controllerName);
…
controller.Execute(controllerContext);

Controller

As we saw in part 1, Controllers are like the middleware of ASP.NET MVC web application. They take the request for a certain action, do any processing, such as retrieving data from the model, then invoking a view. As described above, the Controller for the request is found then its Execute method is called. In the Execute method the Action for the request is found and attempted to be invoked
string actionName = RouteData.GetRequiredString("action");
if (!InvokeAction(actionName))
{
HandleUnknownAction(actionName);
}

The InvokeAction method uses some reflection to find the method for the action and pass any parameters necessary.

This is a brief look at the inner workings of the URL Routing mechanisms. A much more detailed review can be found here, http://www.cnblogs.com/shanyou/archive/2008/03/22/1117573.html

Defining and Creating Routes

Now that we have an understanding of what is happening behind the scenes, we can now concentrate on creating and defining routes to be used in an application.

Routes are added to the RouteTable for the application in the Application_Start event.

protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

This is a default implementation for an ASP.NET MVC application. Routes are evaluated in the order in which they have been entered into the RouteTable. The first entry, IgnoreRoute, is an extension method on RouteCollection provided by MVC.

public static void IgnoreRoute(this RouteCollection routes, string url, object constraints)
{
    if (routes == null)
    {
        throw new ArgumentNullException("routes");
    }
    if (url == null)
    {
        throw new ArgumentNullException("url");
    }
    Route route2 = new Route(url, new StopRoutingHandler());
    route2.Constraints = new RouteValueDictionary(constraints);
    Route item = route2;
    routes.Add(item);
}
This method creates a new Route that uses the StopRoutingHandler to stop processing requests for HTTPHandlers. This is essential for processing request in MVC since, as we saw above, requests are redirected to UrlRouting.axd. Routes are added to the collection using MapRoute, which is an extension method to RouteCollection provided by MVC with three overrides.
public static void MapRoute(this RouteCollection routes, string name, string url)
public static void MapRoute(this RouteCollection routes, string name, string url, object defaults)
public static void MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints)
The first two overrides call the last, which creates a Route that uses the MvcRouteHandler then adds it to the collection.
Route route = new Route(url, new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints)
};

if(String.IsNullOrEmpty(name))
{
    // Add unnamed route if no name given
    routes.Add(route);
}
else
{
    routes.Add(name, route);
}
If a Route that did not use the MvcRouteHandler it could, of course, be added directly to the collection.
routes.Add(new Route("Default.aspx", new{ controller = "Home", action = "Index"}, new MyRouteHandler);

Routing in action

With URL Routing you can accept requests such as http://www.mysite.com/Products/Bikes/Schwinn, and have it, for instance, display all bikes made by Schwinn. Rather than using query string parameters this produces a much cleaner URL and routing details can be hidden from ordinary users. ASP.NET MVC expects URLs to have at least two elements, controller and action. In the URL http://www.mysite.com/Home/Index, Home is the controller that should be used, and Index is the action to be invoked. A Route for this would be as follows.

Route("{controller/{action}", 
new RouteValueDictionary( new { controller = "Home", action = "Index" }), 
new MvcRouteHandler());
The first parameter is the URL pattern to match requests against. The second parameter is a RouteValueDictionary that can be used to provide default values if not found in the requested URL. In the case of a request for http://www.mysite.com would be rewritten as http://www.mysite.com/Home/Index. A request for http://www.mysite.com/Home would also be rewritten as http://www.mysite.com/Home/Index

Routes are evaluated in the order they were added to the RouteCollection. For instance, given the routes added in this order

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

routes.MapRoute("NameRoute", "Artist/{name}",
    new { controller = "Music", action = "ArtistsByName", name = "AC/DC" }
);
A URL such as http://www.mysite.com/Artist/Elvis will be matched by the first route despite it having the word Artist. The first Route assumes that Artist is meant to be the controller and Elvis the action, a default id of 0 will be inserted. To get the desired results, the Routes need to be reversed in the collection. Given the two Routes below the problem is determining which is used for requests such as http://www.mysite.com/Artist/1394 and http://www.mysite.com/Artist/U2
routes.MapRoute("ArtistByID", "Artist/{id}",
    new { controller = "Artist", action = "AlbumsByArtistId", id = 0 }
);

routes.MapRoute("ArtistByName", "Artist/{name}",
    new { controller = "Artist", action = "AlbumsByArtistName", name = "" }
);
In the former URL it’s obvious that what is being provided is an ID, however, in the letter URL U2 is assumed by the engine to be an ID and will attempt to case it to an integer as required by the AlbumsByArtistId action method. If the Routes were reversed, 1394 would be converted to string as required by AlbumsByArtistName. To solve this problem we can make use of the Constraints parameter when creating the Route. This parameter is a RouteValueDictionary that is used to a Regular Expressions to be used to evaluate a specified parameter in the requested URL.
protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, 
string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
    object obj2;
    IRouteConstraint constraint2 = constraint as IRouteConstraint;
    if (constraint2 != null)
    {
        return constraint2.Match(httpContext, this, parameterName, values, routeDirection);
    }
    string str = constraint as string;
    if (str == null)
    {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, 
             RoutingResources.Route_ValidationMustBeStringOrCustomConstraint, 
             new object[] { parameterName, this.Url }));
    }
    values.TryGetValue(parameterName, out obj2);
    string input = Convert.ToString(obj2, CultureInfo.InvariantCulture);
    string pattern = "^(" + str + ")$";
    return Regex.IsMatch(input, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
The above Routes can be modified as follows
routes.MapRoute("ArtistByID", "Artist/{id}",
    new { controller = "Artist", action = "AlbumsByArtistId", id = 0 },
    new { id = @"\d{1,}" }
);

routes.MapRoute("ArtistByName", "Artist/{name}",
    new { controller = "Artist", action = "AlbumsByArtistName", name = "" },
    new { name = @"[a-zA-Z]{1,}" }
);
Now the request for http://www.mysite.com/Artist/1394 will be evaluated as matching the ArtistByID Route and http://www.mysite.com/Artist/U2 by the ArtistByName Route regardless of the order they appear in the RouteTable.

To be continued…

ASP.NET MVC is a very rich technology that can’t be covered in a single article. Hopefully this article has illustrated the basic concepts and can be used to evaluate the potential of this technology. Future articles in this series will cover a more aspects such as unit testing and forms.

References

http://www.asp.net/downloads/3.5-extensions/
http://weblogs.asp.net/scottgu/archive/2007/10/14/asp-net-mvc-framework.aspx

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mark Nischalke


Supporter
My first job was coding COBOL on Mainframes but I soon progressed to DOS, then to Windows. I've been a consultant for a number of years and have been involved in a variety of projects, from Finite Element Analysis systems to train control systems to large e-commerce websites to one page static websites. I attained my MCSD status in 1998 with the WOSA exams, updated it with VC++ and transitioned to MCSD.NET, grabbing MCAD.NET along the way. I've been working almost exclusively with C# and .NET since it was in beta.
Occupation: Other
Location: United States United States

Other popular ASP.NET articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 2 of 2 (Total in Forum: 2) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralAlternative webservermemberVerifier23:03 7 Jul '08  
GeneralRe: Alternative webserversupporterMark Nischalke3:04 8 Jul '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 5 Jul 2008
Editor:
Copyright 2008 by Mark Nischalke
Everything else Copyright © CodeProject, 1999-2008
Web07 | Advertise on the Code Project