Over the last few years, SEO has grown into an expertise all its own. Everyone wants their sites to be “SEO friendly”, or optimized for search engines. Until now, creating dynamic sites using ASP.Net web forms that were SEO friendly was a daunting and tedious task.
Now, in ASP.Net 4.0 and the introduction of System.Web.Routing namespace, creating SEO friendly sites just became very easy. There is a lot of buzz around MVC, not a new concept, but new’ish to ASP.Net. Currently, MVC is in version 2.0 and for those that don’t know what MVC is or what it stands for, MVC is an acronym for “Model View Controller”. While this mini tutorial is not about MVC at all, System.Web.Routing was ported to it’s own library from the MVC framework so that the benefits of routing can be used in ASP.Net web forms too.
So, why is routing cool? Take a look at the following url’s:
/store/product.aspx?catid=43&prodid=123&color=red&prod=notebook&desc=windows7
vs.
/store/43/123/red/windows7/notebooks
I don’t need to ask which URL is more SEO friendly or more descriptive. Before the introduction of the new routing capabilities, URL re-writing was typically used to redirect users from friendly (display) URL’s to a handler. I won’t get into how the “old” way was done, but I will show you how to achieve this with routing.
System.Web.Routing has many properties, methods, etc. available to the user. You can perform routing on a page or define routes globally using the global.ascx file.
It works by creating a routing table, routing dictionaries and declaring a handler/form for each route. You could use one file to handle all routes, but this would likely get very messy, very fast.
For this example, we are only going to have a single route defined that can handle all articles we will publish. The first thing we need to do is create a new web site that targets the 4.0 framework. Next we will need to open the global.ascx file and add a method that will register our routing table, as well as calling this method in our application_startup method.
Inside the global.ascx file, add a reference to System.Web.Routing and add a call to RegisterRoutes method we will create later inside your Application_Start method:
<%@ Import Namespace="System.Web.Routing" %>void Application_Start(object sender, EventArgs e) { // register rout mapping tables RegisterRoutes(RouteTable.Routes); } public static void RegisterRoutes(RouteCollection routes) { routes.MapPageRoute("ArticleRoute", "articles/{aid}/{colid}/{artname}", "~/news/default.aspx", false, new RouteValueDictionary { { "aid";, "1" }, { "colid", "1"}, { "artname", "invalid-article" }}, new RouteValueDictionary { { "aid", "[0-9]{1,8}"}, { "colid", "[0-9]{1,8}" } });
**Notice in the above snippet, we not only defined our route and parameters, but also constraints for our parameters in our second, but optional RouteValueDictionary object. More on this later.
Now, once we have our routes defined and registered, we need to create our form that will handle all requests that map to our article route.
Go ahead and create a new folder and add a new web form (default.aspx). Now that we have our form built, we need to add logic to the forms to get the parameters from the routed request so we can use them later as parameters to fetch data, manipulate stuff or load other dynamic controls.
Earlier, I wrote that accessing these parameters could be “as simple, if not more simple and more secure…” Lets look at how to access these parameters.
protected void Page_Load(object sender, EventArgs e) {
//Using a loop, we can grab all the key-value-pairs that is accessible via
//Page.RouteData.Values collection.
foreach (KeyValuePair<string, object> kvp in Page.RouteData.Values){
lblAllValues.Text += "Key: " + kvp.Key + " Value: " + kvp.Value + "
";
}
//Grabbing the routing values using a loop might not be too useful.
/We will access them individually.
Int32 articleid = Convert.ToInt32(RouteData.Values["aid"]);
Int32 columnid = Convert.ToInt32(RouteData.Values["colid"]);
string articlename = RouteData.Values["artname"].ToString();
Ok, we can see that this is at the very least, as simple as grabbing values from the query string. So, how is this more secure? Take a look at the routes we defined earlier. The MapPageRoute takes a set of parameters:
-
It needs a routing key.
-
It needs the mapping path. E.g. the path of the public URL you provide to the end users that maps to the physical path of the file that will handle the request.
-
It *needs* a Route value dictionary, where you define the parameter names, and their default values
-
Optionally, you can add an additional RouteValueDictionary object to list constraints for those values.
If we choose to enter constraints, this means the parameters must fall within an acceptable range. If they don’t, it is assumed it is a bad path, and a 404 will be generated. Thus, disallowing bad parameters from reaching the file handler. This has it’s pro’s and con’s. Pro's: initial rigor, or first step validation. Cons: the handler can’t make a provision for the disallowed parameters as it’s never hit.
What if we want to enforce some constraints for our parameters, but not for all? No problem. You can define a constraint for one, all, some or none of your parameters – it’s pretty flexible and should accommodate almost any situation.
Can you use both query string and routing values? Yes you can. Below is an example of how you would make provisions using a wild card “*” character in your route definition parameters.
/*Article routing with wildcard to handle additional qs params*/
routes.MapPageRoute("ArticleRoute", "articles/{aid}/{colid}/{artname}/{*qsparams}", "~/news/default.aspx", false,
new RouteValueDictionary {
{ "aid", "1" },
{ "colid", "1"},
{ "artname", "invalid-article" }},
new RouteValueDictionary {
{ "aid", "[0-9]{1,8}" },
{ "colid", "[0-9]{1,8}" } });
}[/pre]
Notice the additional information we included in our mapping {*qsparams} now our path will match and be routed to the handler with or without additional information in the querystring.
One last note, if you have routes that are similar, the first route that is matched is the one that is used. This means you should have clearly defined routes and in order of activity. For example, if your "articles" page is accessed most often, it should be the first route added. Conversely, a page that is visited the least, should be added last.