![]() |
Web Development »
ASP.NET »
General
Intermediate
License: The Code Project Open License (CPOL)
ASP.NET MVC Part 2By Mark NischalkeA continued look at ASP.NET MVC and URL Routing |
C# (C#3.0), .NET (.NET3.5), ASP.NET, WebForms
|
||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
ASP.NET 3.5 Extensions
ASP.NET MVC Preview 3 http://www.asp.net/downloads/3.5-extensions/
MusicCatalog database, included in download
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 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
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 file, under the web.config httpModules element places the into the pipeline for a web application. UrlRoutingModule
<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"/>
OnApplicationPostMapRequestHandler, OnApplicationPostMapRequestHandler in its Init method. The latter is, of course, used to handle the event on the PostMapRequestHandler 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 HttpApplication 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 PostResolveRequestCache object is created and added to the RequestData collection. An instance of an HttpContext interface is added and IRouteHandler is used to redirect the request to HttpContext.RewritePath UrlRouting.axd
RequestData data2 = new RequestData();
data2.OriginalPath = context.Request.Path;
data2.HttpHandler = httpHandler;
context.Items[_requestDataKey] = data2;
context.RewritePath("~/UrlRouting.axd");
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 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 method uses some reflection to find the method for the action and pass any parameters necessary. InvokeAction
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
Routes are added to the for the application in the RouteTable 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);
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.
| You must Sign In to use this message board. | |||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 5 Jul 2008 Editor: |
Copyright 2008 by Mark Nischalke Everything else Copyright © CodeProject, 1999-2010 Web09 | Advertise on the Code Project |