Introduction
The 404 or Not Found error message is an HTTP standard response code, there are many reasons for a 404 to show up like,
- Mistyped URL
- Pages have been moved.
- Pages have been deleted.
The default error page for 404 may look something similar to this:

The purpose of this blog/article is to discuss ways to handle 404 smartly in ASP.NET.
So the big question is how? On an event of 404, the first thing I would try do is to catch the event and redirect to somewhere where I can handle it in a more nicer way. In ASP.NET its just a matter of second, simply add the following node in your web.config thats it.
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
<customErrors mode="On">
<error statusCode="404" redirect="~/error404.aspx" />
</customErrors>
What we have done here is on an event of 404 we are redirecting to a custom "error404.aspx" page.
Now common practice is to display some decent error messages or links on this page, but we can really do lot more than that, one thing to note here is we are redirecting to an ASPX page and we can do smart coding in the code behind file.
- In an event of mistyped url we can suggest valid pages (Intelligent 404).
- In case of deleted / moved page we can redirect smartly to the correct page.
Lets look at how we can implement this.
Intelligent 404
To be able to suggest potential valid pages intelligently,
- First, we need to know what we are searching for.
- Second, we need to know the available pages in the website.
- Third, we need to create uri list from the available file list.
- Forth, we need to match the page that we are looking for, in the urilist and suggest the potential uris.
Lets look at all this in code:
This is how we can find out what we are looking for:
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
string from = Request.QueryString["aspxerrorpath"];
int index = from.LastIndexOf("/") + 1; string search = from.Substring(index).Replace(".aspx", string.Empty).Replace("-", " ");
Then we need the list of available files, the following method filters by file extension and return all the list of files (including files in subfolder).
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
private List<string> GetAllFiles(string directory, string filter, bool getFilesInSubDirs)
{ List<string> filesList = new List<string>();
string[] files = null;
if (filter == null)
{ files = Directory.GetFiles(directory);
}
else
{ files = Directory.GetFiles(directory, filter);
}
filesList.AddRange(files);//add all files in that current folder.
if (getFilesInSubDirs)
{ //Check if the current directory has sub-directories
string[] subDirs = Directory.GetDirectories(directory);
//if yes, then call recursive functions..
if (subDirs.Length > 0)
{ //now look for all files in current folder's sub-dir's.
foreach (string subDir in subDirs)
{ List<string> tempList = GetAllFiles(subDir, filter, getFilesInSubDirs);
filesList.AddRange(tempList);
}
}
}
return filesList;
}
After getting the files we need to turn them into proper uris.
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
public List<string> GenerateUriList(string directory, string rootUri)
{ List<string> filelist = GetAllFiles(directory, "*.aspx", true);
List<string> uriList = new List<string>(filelist.Count);
foreach (string str in filelist)
{ string uri = str.Replace(directory, rootUri);
uri = uri.Replace(@"\", "/");
uriList.Add(uri);
}
return uriList;
}
We also need to be able to match and find the potential uris, like this,
bool GetMatchingUri(string search, string uri)
{ //case in sensitive match
string term = search.ToLowerInvariant().Trim();
string[] terms = term.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string regex = string.Format(System.Globalization.CultureInfo.InvariantCulture, "({0})", string.Join("|", terms)); int result = (Regex.Matches(uri.ToLowerInvariant(), regex).Count);
return result > 0;
}
And finally the Suggest() method which uses all the above codes and makes the 404 more intelligent, and suggests potential Uris. You can call this during OnPage_Load event of your error404.aspx.
public partial class error404 : System.Web.UI.Page
{ protected void Page_Load(object sender, EventArgs e)
{ if (Request.QueryString["aspxerrorpath"] != null)
{ Suggest();
}
}
}
private void Suggest( )
{ string from = Request.QueryString["aspxerrorpath"];
int index = from.LastIndexOf("/") + 1; string search = from.Substring(index).Replace(".aspx", string.Empty).Replace("-", " "); //
string rootDirectory = Request.PhysicalApplicationPath;
string rawurl = Request.RawUrl;
int index1 = rawurl.LastIndexOf('/'); //Get the virtual root directory.
string virtualDirectory = rawurl.Substring(0, index1 + 1);
//Get UriList
List<string> uriList = GenerateUriList(rootDirectory, "");
foreach (string uri in uriList)
{ if (GetMatchingUri(search, uri))
{ LiteralControl result = new LiteralControl(string.Format("<li><a href=\"{0}\">{1}</a></li>", uri, uri)); phSearchResult.Controls.Add(result);
}
}
}
here phSearchResult is a ASP.NET Placeholder control.
The end result may look something like this, where I intentionally typed an url (localhost/Albums.aspx) which does not exist, notice how the error404.aspx intelligently suggests the correct urls instead.

Redirecting smartly to the correct Page
Another common responsibility of a Web Admin is to be able to point to the right pages when there is a change/move/rename/delete of existing pages. The referers/search engines may still cache the old url and use it, but the website need to handle this situation and properly redirect to the correct page/pages. In ASP.NET this is again pretty easy, by using the same technique mentioned above we can redirect the page to error404.aspx and handle this in the code behind file.
To achieve this, first thing we need is some sort of database/xml where we can define old and new url mappings.
<?xml version="1.0"?>
<ArrayOfUrlMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<UrlMap>
<OldUrl>?404;http://www.mysite.com/albums.htm</OldUrl>
<NewUrl>~/Albums.aspx</NewUrl>
</UrlMap>
<UrlMap>
<OldUrl>?aspxerrorpath=/albums.html</OldUrl>
<NewUrl>~/Albums.aspx</NewUrl>
</UrlMap>
</ArrayOfUrlMap>
Note, I have put two types of url, the reason is, IIS and ASP.NET development Server generates different urls.
string queryString = Request.Url.Query.ToString();
//result of the above line will be different based on IIS of ASP.NET
//?404;http://www.mysite.com/albums.htm (generated by IIS)
//?aspxerrorpath=/resume.html (generated by ASP.NET development Server)
Secondly we need to match the oldUrl with the list in the database/xml and redirect to the right location.
protected void Page_Load(object sender, EventArgs e)
{ string queryString = Request.Url.Query.ToString();
string match = FindMatchingPage(queryString);
if (match != null)
{ //Set the Response status code to 301 (Moved Permanently)
//redirect to the new Url
}
else
{ //Do Something else ie Suggest()
}
Response.End();
}
private string FindMatchingPage(string queryString)
{ //search in your db/xml
//if (queryString == oldurl)
//return matching new url
}
And thats it we are done.
Conclusion
In ASP.NET its very easy to handle 404, I have just demonstrated 2 smart ways of handling 404.
I hope this helps and thank you for being with me so far.