June 7, 2007

How To Catch HttpRequestValidationException

If you've worked with ASP.NET for any length of time, you have worried about how to handle the Yellow Screen of Death. You know, the one that says:
HttpRequestValidationException: A potentially dangerous Request.Form value was detected from the client...
or something similar.

The common refrain is turn off request validation, but there are times when you want to use the built-in validation functions. You just want a friendlier error for your user.

Most people try to catch the exception in their Global.asax Application_Error handler, but it acts like it can't catch it. The reason? Your request must be completed before the Application_Error block finishes, or the Yellow Screen of Death will appear again.

Here is some code that will generate a slightly more user-friendly error page. Feel free to extend it or use it as you will.



void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();

if (ex is HttpRequestValidationException)
{
Response.Clear();
Response.StatusCode = 200;
Response.Write(@"
<html><head><title>HTML Not Allowed</title>
<script language='JavaScript'><!--
function back() { history.go(-1); } //--></script></head>
<body style='font-family: Arial, Sans-serif;'>
<h1>Oops!</h1>
<p>I'm sorry, but HTML entry is not allowed on that page.</p>
<p>Please make sure that your entries do not contain
any angle brackets like &lt; or &gt;.</p>
<p><a href='javascript:back()'>Go back</a></p>
</body></html>
"
);
Response.End();
}
}

(Update: Fixed an HTML entity issue; also fixed layout for people using IE6.)

12 comments:

Sarkie said...

Do you have to add the mime content type just before you finish, just in case the browser is stupid?

SLORider said...

Thanks for your code. I extended it so it outputs a more professional error page---instead of writing out an in-line page, I create an HttpWebRequest to my own web site (to a special error page), then copy the HttpWebResponse out. In the process, I copy the headers and status code as well. The result is the user gets a full-fledged .aspx page (with master page, etc.) in response to the error!

This code could be better thought out, but here it is anyway:

void Application_Error(object sender, EventArgs e)
{
// Code that runs when an unhandled error occurs

Exception ex = Server.GetLastError();

if (ex is HttpRequestValidationException)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
"http://" + Request.Url.Authority + Request.ApplicationPath +
"/HttpRequestValidationException.aspx");
HttpWebResponse response = (HttpWebResponse)req.GetResponse();

Response.Clear();
Response.ClearHeaders();
for (int i = 0; i < response.Headers.Count; i++)
Response.AddHeader(response.Headers.GetKey(i), response.Headers.Get(i));
Response.StatusCode = (int)response.StatusCode;

System.IO.Stream src = response.GetResponseStream();
while (true) {
int b = src.ReadByte();
if (b == -1) break;
Response.OutputStream.WriteByte((byte)b);
}

response.Close();
Response.End();
}

}

Huan said...
This comment has been removed by the author.
htn0200 said...

You're out-thinking things, aren't you? Do this:

protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();

if (ex is HttpRequestValidationException)
{
Response.Redirect("FriendlyMessage.aspx", true);
}
}

Thomas said...

Was searching for this and find this useful.

Bookmarked:
http://codebounce.com/ASPNET

Jez said...

where are you putting this code? Does it go in the page load event - or somewhere else?

SLORider said...

@jez-- The Application_Error() method is an application-level method that receives all otherwise unhandled errors (errors that you don't catch in your code). It is not a page-level event, so it doesn't go in your page code. Application_Error() goes in global.asax or can be compiled into a class that inherits HttpApplication (instead of using global.asax, create a code-only module (.cs or .vb) in your App_Code directory, or compile it into an assembly and place the assembly .dll in your bin directory). Hope this helps.

Jez said...

thanks dude - I thought from the description that it couldn't go in your global.asax file because it couldn't be caught until the request completed. Re-reading it I guess it means that it will look like it's not going to be caught but then it will be?
I think I'll add it into a separate class file and place it in the App Code folder.

thanks again,

Jez

SLORider said...

@jez-- The key sentence is, "Your request must be completed before the Application_Error block finishes, or the Yellow Screen of Death will appear again." That's why the author's code includes Response.End().

Honestly, I haven't experimented with this long enough to be much of a guru, and looking at it some more I think you could catch the exception in your page class somewhere, but then it would only apply to that page. Handling the error in your HttpApplication class (global.asax) means every page with a form on it is protected.

There's obviously a lot of room here for varying solutions. Have fun!

danbriapps said...

I know this post is 4 years old but I wanted to say thanks!

Also, to get this working I had to add Server.ClearError() before Response.Clear(). After that it worked perfectly.

nik.agarwal said...

thanks , it works :)

Hagen Mobiler said...

2015 year and still working!
Just that I need, a different message instead YSoD.
Now I'm going to redirect a page acording to my web application.
Thanks