Tuesday, October 16, 2007

Custom 404 Errors and Issues they Create

When you setup a custom 404 error in you Web.Config file, it only handles invalid URL's that have a file extension on them. For instance, www.masonblake.net/nofile.aspx will be caught by ASP.NET and properly send you to the custom 404 page you have setup (/404.aspx). But when you enter a URL without a file extension, say www.masonblake.net/somefolder/invalidpath, ASP.NET does not catch this.

What I've done is set this 404 custom error in both the web.config and in the IIS Custom Errors tab (in the properties of your website or virtual directory). But this also causes some issues, especially if your custom 404 page has forms that post back data for searching, logging in, or anything like that. Here's what happens. (btw, this is not setup on my domain in which I use the examples)

  1. You enter an invalid path, www.masonblake.net/nofolder/nofile

  2. This is caught as a 404, File Not Found and sends you to /404.aspx

  3. So you didn't find what your looking for and decided to search on this 404 page. You enter "Melodic Jones" into the search box and click "Search"

  4. Then an error happens!! Because the form you just tried to post thinks that it's posting back to /nofolder/404.aspx when actually it needs to post to /404.aspx.

    • As far as I understand.... when IIS does a custom error catch, it does a Server.Transfer, whereas ASP.NET (with Web.Config) does a Response.Redirect.

    • That means this issue doesn't arrise when you put in a file extension in your invalid path, only when there is no file extension on your invalid URL

So now, you're effectively getting 2 errors for every 404 Page Not Found. Ouch, your error logs are getting big!

What I've done to work around this is I've taken Scott Mitchell's article on "Actionless Forms" and modified it myself to make this a FullUrlForm control which inherits from HtmlForm. Here is the implementation.

What I wanted to do was be able to set the Form control's Action attribute to the full URL on that page, so it would never be confused about the path it should post back to. After much toying around, I was not able to directly modify the HtmlForm's Action attribute without creating my own custom control. Below, I've created a new HtmlForm control, "FullUrlForm".


public class FullUrlForm : HtmlForm
{
private string _fullUrlAction;

public string FullUrlAction
{
get { return _fullUrlAction; }
set { _fullUrlAction = value; }
}

protected override void RenderAttributes(HtmlTextWriter writer)
{
writer.WriteAttribute("name", this.Name);
base.Attributes.Remove("name");

writer.WriteAttribute("method", this.Method);
base.Attributes.Remove("method");

//write the full URL action, specified in the property
// or, if not set, don't write the action at all
if (!string.IsNullOrEmpty(this.FullUrlAction))
writer.WriteAttribute("action", _fullUrlAction);
base.Attributes.Remove("action");

this.Attributes.Render(writer);

if (base.ID != null)
writer.WriteAttribute("id", base.ClientID);
}
}


So then on your page, you need to add the Register attribute, like so:
<%@ Register TagPrefix="MyControls" Namespace="MySite.UI" %>

Then replace the <form> tag in your page with this:
<MyControls:FullUrlForm id="form1" runat="server">
....
</MyControls:FullUrlForm>

It has it's own new property FullUrlAction, which can be set in the control as an attribute or programmatically in the code-behind. I've set it in the code behind, like below, and use the site root variable which I've set globally in order to easily access the root domain/URL of the server I'm currently on.

form1.FullUriAction = string.Format("{0}/404.aspx", Root);

To use it as a tag attribute, simply edit your tag to look like this:
<MyControls:FullUrlForm id="form1" runat="server" fullurlaction="http://www.masonblake.net/404.aspx">
....
</MyControls:FullUrlForm>

0 comments:

Post a Comment