Category Archives: ASP.NET

301 redirects

While ASP.NET MVC has a powerful routing engine for handling requests, there can be a scenario when you need to process a list of specific URL rewrites in your application. Often this can be due to a new version of an existing site going live where the URL structure has changed. Under Apache, this can be handled easily in the .htaccess file of the site by listing out each path  you’d like to rewrite like so:

1
2
3
4
<IfModule mod_rewrite.c>
  RewriteEngine on
  Redirect 301 /somepage.html /otherpage.html
</IfModule>

It can also be handled in IIS 7 using the URL Rewrite module or under IIS 6 using a 3rd party extension such as Helicon ISAPI Rewrite.

But what if you want to handle the redirects via code?

Bulk 301 Redirect – Considerations

Our two main considerations when coming up with this solution were:

  1. We don’t want to check for a redirect on every request (just old broken links)*
  2. We don’t want to have to do a code push to change the 301 mapping list

*: You can use the same solution to process every request by changing the location of the 301 check

This led us down a path where we are capturing 404 requests in the application, then checking for a rewrite rule before actually displaying a 404 page. If the rule exists, we rewrite the path instead of showing the 404.

The 301 List

With consideration #2 in mind, we decided to use a flat file in the App_Data folder to hold our rewrite rules. The file is simple since all we are concerned with is permanent redirects. We use a plain old CSV file with 2 columns, the first column being the old path to check for, the second column is the new path to permanently redirect to. For example:

1
2
/Home/FollowUs,/About/Follow-Us
/Home/ContactUs,/About/contact-us

This makes constructing, updating, and maintaining the rewrite list extremely easy. Even better, since it’s a CSV file, you can use excel to have a non technical person create the list for you if it’s lengthy.

To process the list, we created a single static method to load the list into a key,value Dictionary object. Here is the entire class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
namespace CypressNorth.Data.Services
{
    public class FlatFileAccess
    {
        public static Dictionary<string,string> Read301CSV()
        {
            string file = "App_Data/301.csv";
            string path = System.IO.Path.Combine(AppDomain.CurrentDomain.GetData("DataDirectory").ToString(), file);
            if (File.Exists(path))
            {
                using (TextReader sr = new StreamReader(path))
                {
                    Dictionary<string,string> redirect_dict = new Dictionary<string,string>();
                    string line = "";
                    while ((line = sr.ReadLine()) != null)
                    {
                        string[] columns = line.Split(',');
                        redirect_dict.Add(columns[0], columns[1]);
                    }
                    return redirect_dict;
                }
            }
            else
                return null;
        }
    }
}

This will process your list of rewrite rules into a Dictionary<string,string> list and return it for you.

Intercepting 404 responses

Now that we have our list of 301 redirects and a way to access them, we need to hook into the app request life cycle to process them. Our consideration #1 states that we don’t want to process every single request to the site, just bad URL’s so the logical place for this is when a 404 response is encountered.

To hook into this we create a new method in the global.asax file. You could do this via an HttpModule as well if you prefer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void Application_Error()
{
    Exception exception = Server.GetLastError();
    // Clear the error
    Response.Clear();
    Server.ClearError();
    if (exception is HttpException)
    {
        HttpException ex = exception as HttpException;
        if(ex.GetHttpCode() == 404)
        {
            var url = HttpContext.Current.Request.Path.TrimEnd('/');
            var redirect = FlatFileAccess.Read301CSV();
            if (redirect.Keys.Contains(url))
            {
                // 301 it to the new url
                Response.RedirectPermanent(redirect[url]);
            }
        }
    }
}

This method will catch Exceptions application wide. You can implement much more robust error handing using the same hook but for our purposes we just want to catch 404′s and check for redirects. So when an error is thrown in the application, the exception is checked to see if it is a 404 type HttpException. If it is, we load the 301.csv file and compare it against the requested path. If a match is found we 301 redirect the request to the new url. If it is not found, we let the error fall through.

ASP.NET Web API 2.1 RC is out

The ASP.NET team kept it’s frantic work pace and already before Christmas dropped onNuget a 2.1 RC of Web API (as well as new MVC and Web Pages).

While in the open source mode of operation (public commits, nightly builds) this is that big of a news, it is still a very nice piece of news. Let’s look at what’s new.

Overview of ASP.NET Web API 2.1 RC

The official release notes are available here. Most of the features you have already seen – as on this blog we try to cover both existing and the upcoming bits of the Web API framework.

You can grab the release from Nuget:

Shell
1
Install-Package Microsoft.AspNet.WebApi -Pre

 

Glboal error handling

Arguably the biggest feature of this drop – something that has been very heavily discussed i nthe advisors meetings and in the discussion list.

The full feature specification is available here.

Now, in short, you get a single point of error handling for your Web API – something that didn’t exist before, and trying to handle all errors required you to catch in many places (or resort toGlobal.asax).

Up to this point you could use ExceptionFilters for more or less widely-scoped error handling; but they wouldn’t catch exceptions from message handlers, media type formatters or routing.

The simplest example is absolutely trivial:

C#
1
2
3
4
5
6
7
public class TraceExceptionLogger : ExceptionLogger
{
    public override void LogCore(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

 

BSON formatter

Web API now supports application/bson out of the box. We have written a BSON formatter on this blog back in the summer of 2012 and it’s also a part of WebApiContrib, but it’s nice to have it in the core, especially as it doesn’t bring in any external dependencies (it relies on JSON.NET).

It has also been hardedned a bit, so that’s another plus.

Async filters

I have just recently blogged about async filters for Web API. As mentioned there, the vNext will have it in the box, and as a result, you can enjoy this feature in Web API 2.1 RC.

Ignore routes

HttpRoutesCollection will now support IgnoreRoute mechanism, allowing you to easily exclude parts of your routing hierarchy from being matched.

Attribute routing enhancements

Attribute routing now provides new extensibility hooks. They are described in detail here (bottom of the page) and allow you to provide instructions on custom route resolution (IDirectRouteProvider). This is extremely useful when trying to do something like versioning or localization.

Fixes, fixes, fixes

Plenty of bug fixes too. Among the more interesting ones:

Web API 2’s Attribute Routing: Looking Deeper

While most of us are used to creating routes using the Web API configuration, attributed routes are different. Before attributed routing, when you would look at the route data (e.g. request.GetRouteData()) you would get a route with a name that tied to the configured name. This was really useful for the way to use UrlHelper to build your URLs.

In fact, you can get this behavior by supplying names to the individual routes in your attributed routing:

1
2
3
4
5
6
7
8
9
10
11
12
13
public FooController : ApiController
{
  [Route("api/foos", Name = "Foo")]
  public object Get()
  {
    // ...
  }
}
...
var helper = new UrlHelper(Request);
var url = helper.Link("Foo");

But if you’re traversing the route collection in any way (e.g. in a ControllerSelector) it is important to understand where these routes actually are. When you use attribute routing, all the route attributes get added to a common route without a name. This is a special route that is an instance of an internal class called RouteCollectionRoute (Source Link). This route has a collection of sub-routes that you can query for that includes *all* the attribute routes. But if you just want the selected route for your call, you can simple ask for it using the RouteData.Values:

1
2
3
var routeData = request.GetRouteData();
var subroutes = (IEnumerable<IHttpRouteData>)routeData.Values["MS_SubRoutes"];
var route = subroutes.First().Route;

The real problem for some is that there is no longer controller name in the route data. This makes sense of course because there is no specific controller that a route points to as attributed routes are related to methods, not controllers. Being aware of these internals may help you solve issues when you use or move to using attributed routes.