IIS 7 Error Pages taking over 500 Errors
Recently I’ve run into a problem on my production server where results that produce errors that are handled by the application (either in Application_Error or in some cases for JSON or XML based custom services) would return IIS error pages instead of my custom error content generated in code. Basically what’s happening in all of this handled error code is that the application somehow intercepts an error – either Application_Error, or in the case of a JSON service an exception in the RPC call for example – and an error handler kicks in to handle the problem. The error handler then sets the status code to 500 and generates some output that is appropriate. For Application_Error a custom HTML page is generated . For the JSON service a custom JSON error object is returned.
For example the following is the error output routine in my custom JSON Request processor:
/// <summary> /// Returns an error response to the client from a callback. Code /// should exit after this call. /// </summary> /// <param name="ErrorMessage"></param> public void WriteErrorResponse(string errorMessage, string stackTrace) { CallbackException Error = new CallbackException(); Error.message = errorMessage; Error.isCallbackError = true; Error.stackTrace = stackTrace; JSONSerializer Serializer = new JSONSerializer(); string Result = Serializer.Serialize(Error); HttpResponse Response = HttpContext.Current.Response; Response.ContentType = ControlResources.STR_JsonContentType; Response.StatusCode = 500; Response.Write(Result); HttpContext.Current.ApplicationInstance.CompleteRequest(); //Response.End(); }
The status code is set to 500, and in this case a JSON error object is written to the response. The idea is that the request should generate a 500 error, but still provide appropriately formatted error information – in this case JSON – to the client. This works fine on my development machine on Vista with IIS 7, but fails in the live environment on IIS 7 Windows Server 2008.
On the live server I the response that the server generates is not my JSON object, but rather than HTML error page that IIS generates which results in an error without an information of what went wrong. So instead of a message that lets the user know that something went wrong (like Login first please) the result comes back with a non-descript error message:
rather than the more descriptive error message that should be returned. Not cool!
The above is maybe a little bit specialized of an example since it involves a custom handler and a JSON HTTP response returned. But this issue really affects any server response that needs to return a controlled 500 error. Another example, and that’s likely more inline what most of you are doing: Trying to generate custom dynamic Application_Error messages that display error messages specific to the application and include specific application related information in it that static error pages can handle.
I tend to have fairly elaborate Application_Error handlers that allow me to switch error display modes easily for example, and allow users to create custom error pages through the admin interface. Here’s a small snippet of the handler that returns a generic error message from the application.
When an error occurs in the application Application_Error fires and eventually this code block is fired:
else if (App.Configuration.DebugMode == DebugModes.ApplicationErrorMessage) { string stockMessage = App.Configuration.ApplicationErrorMessage; // Handle some stock errors that may require special error pages HttpException httpException = serverException as HttpException; if (httpException != null) { int HttpCode = httpException.GetHttpCode(); Server.ClearError(); if (HttpCode == 404) // Page Not Found { Response.StatusCode = 404; MessageDisplay.DisplayMessage("Page not found", "You've accessed an invalid page on this Web server. " + stockMessage); return; } if (HttpCode == 401) // Access Denied { Response.StatusCode = 401; MessageDisplay.DisplayMessage("Access Denied", "You've accessed a resource that requires a valid login. " + stockMessage); return; } } // Display a generic error message Server.ClearError(); Response.StatusCode = 500; MessageDisplay.DisplayMessage("Application Error", "We're sorry, but an unhandled error occurred on the server. " + stockMessage); return; } return;
that is supposed to return a custom error page. However, I still get the IIS 500 error page instead:
What’s confusing about this is that the above error is an IIS error not an ASP.NET error. The real confusion here occurs because the error is trapped by ASP.NET, but then ultimately still handled by IIS which looks at the 500 status code and returns the stock IIS error page. There are two layers of indirection at work here – the ASP.NET customErrors settings and the IIS error page handlers.
Specifically this relates to the Error Pages server setting in the IIS admin interface:
which effectively tells IIS how to display errors. What’s not so obvious here is that this setting overrides the local Web.config setting of <customErrors> – if a 500 error is returned the above error kicks in but only if customErrors is kicking in (On or RemoteOnly). This behavior is new to IIS 7 I believe, maybe even new to IIS 7 SP1 and related to the excellent new server information that is displayed on these error pages when running locally. The behavior I describe realistically applies only to Integrated Mode – the classic ISAPI mode still handles errors the way they always were handled.
What really bites about this is that it’s hard to catch this particular issue. In development you typically never actually see this because you’re likely to be running with RemoteOnly for <customErrors<, so at development the behavior is actually just peachy – you see your code generated errors just fine. The error pages are ignored and ASP.NET does as ASP.NET should do. However, once customErrors are on the behavior changes and the IIS error pages take over.
This is just another reason to test your application in a staging environment or at least enable a full non-debug configuration on your application and test. This is also one reason why every app I have I usually have a test page that bombs on purpose to see what the error behavior is so I can quickly test it in the production environment and get an idea what errors do. Now that I think about it it might be prudent to set up a test page that checks both core errors and ‘service’ style errors from things like a JSON or XML service as well.
Anyway, I’d argue that this is a bug. While I agree that IIS should return error pages on completely unhandled errors and display the generic 500 error page, I do feel that IIS should always respect the output generated by the application if there’s no hard error that rippled all the way into the runtime (ie. no Application_Error handler).
Enter Response.TrySkipIisCustomErrors
There’s a solution to this problem with the deftly named TrySkipIisCustomErrors property on the Response object which is tailor made for this particular scenario. In a nutshell this property when set to true at any point in the request prevents IIS from injecting its custom error pages. This flag is new in ASP.NET 3.5 – so if you’re running 2.0 only you’re out of luck.
So in my error handler code above in Application_Error I can now add this flag and bingo I’m back in business getting my own custom error page to display:
// Display a generic error message Server.ClearError(); Response.StatusCode = 500; Response.TrySkipIisCustomErrors = true; MessageDisplay.DisplayMessage("Application Error", "We're sorry, but an unhandled error occurred on the server. " + StockMessage); return;
Same with my JsonCallbackMethodProcessHandler which returns JSON on errors:
public void WriteErrorResponse(string errorMessage, string stackTrace) { CallbackException Error = new CallbackException(); Error.message = errorMessage; Error.isCallbackError = true; Error.stackTrace = stackTrace; JSONSerializer Serializer = new JSONSerializer(); string Result = Serializer.Serialize(Error); HttpResponse Response = HttpContext.Current.Response; Response.ContentType = ControlResources.STR_JsonContentType; Response.TrySkipIisCustomErrors = true; Response.StatusCode = 500; Response.Write(Result); HttpContext.Current.ApplicationInstance.CompleteRequest(); //Response.End(); }
Thanks to Eilon Lipton, and Michael Campbell for pointing out that there is such a beast as Response.TrySkipIisCustomErrors – not where I would have looked for sure.
Good to Go
I’m glad to see that this flag is available, but we still have to remember to use this in every application that requires returning custom errors. I have tons of specialized handler implementations scattered across many (now idle) projects and finding all the places where this is going to take time.
It seems to me that the better default for TrySkipIisCustomErrors should be true to begin with. After all that’s why we run ASP.NET - to be control of our environment.
Other Posts you might also like
The Voices of Reason
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
I had a question similiar to this on Stackoverflow a while back:
http://stackoverflow.com/questions/434272/iis7-overrides-customerrors-when-setting-response-statuscode
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
Later in my search i came across this blog post about the iis7 custom error module and it's configuration: http://blogs.iis.net/ksingla/archive/2008/02/18/what-to-expect-from-iis7-custom-error-module.aspx
There is a description of a property 'existingResponse' (system.webserver/httpErrors) which defines what this module makes if the statuscode is not 200 and there is content in the response. This property is not accessible by the iis7 manager and has to be set by hand to "Passthrough", "Replace" or "Auto". The behaviour of the default mode "Auto" can be influenced with the property 'TrySkipCustomErrors'.
I have not verified this, but i think if you set this property to "Passthrough" you should accomplish the same as if you set 'TrySkipCustomErrors' to true.
# re: IIS 7 Error Pages taking over 500 Errors
Thanks Rick, for the detailed explanation (one of my search queries finally paid off - there's a lot of other stuff out there which is just talking about using the proper codes, and custom error pages etc) and for the simple answer!!!
# re: IIS 7 Error Pages taking over 500 Errors
I've been fighting with IIS7 and the custom error page.
[Response.TrySkipIisCustomErrors = true;] helps me solve this problem.
Thanks!
ps. I just took on a ASP.NET developer role recently and I've start to recognize your blog & picture... =)
# re: IIS 7 Error Pages taking over 500 Errors
Now I feel that I'm totally out of luck as the application I'm trying to deploy is an ASP.NET 2.0 application. Am I supposed to migrate the whole application to 3.5 just to get rid of this stupid bug in IIS7? WTF!!
# re: IIS 7 Error Pages taking over 500 Errors
You can also run your virtual in Classic Mode which has the old behavior of IIS 6 and prior.
# re: IIS 7 Error Pages taking over 500 Errors
You may check this blog post for more details:
http://www.colincochrane.com/post/2008/01/ASP-NET-Custom-Errors-Preventing-302-Redirects-To-Custom-Error-Pages.aspx
I still think your approach is more flexible (as you don't have to map the different HTTP errors to your error page) but in my case (having an ASP.NET 2.0 app), that really saved the day. I'll have a look on Response.TrySkipIisCustomErrors, they probably added this in ASP.NET 2.0 SP1 or SP2. Thanks again Rick.
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
Thanks so much for this pointer. Saved the day. This is exactly what we do and what I needed.
Kind regards,
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
see this (bottom of the page):
http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors.aspx
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
I would not have known you solution in a million years time, you saved me after spending two days pulling my hair.
# re: IIS 7 Error Pages taking over 500 Errors
It seems that the *only* way to send your own (carefully crafted and non-security-violating) detailed error information *without* A. enabling "detailed errors" to the world (which is too open) and without B. resorting to custom pages (which have no idea what the error was) and *without* C. reverting to a non error status (hiding the real status) is to:
find the httpErrors node in your web.config file (or higher if desired)
add
existingResponse="PassThrough"
(or change existingResponse if it is there already)
You can then write eg
Response.Status = 500
Response.Write "the funglimator conflicted with the obviation policy becuase the secret sauce value supplied [removed] was not correct"
oh my -what a mission!
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
You've saved us alot of work with this article.
# re: IIS 7 Error Pages taking over 500 Errors
C:\Users\<USERNAME>\Documents\IISExpress\config or right click the IIS Express icon on the tray and view all application and click the desired website and then click the configuration file link.
Comment out
<error statusCode="500" prefixLanguageFilePath="%IIS_BIN%\custerr" path="500.htm" />
Set the parent node to <httpErrors errorMode="DetailedLocalOnly"
Set the httpErrors to Allow
<section name="httpErrors" overrideModeDefault="Allow" />
Change your web project web.config HttpErrors
<httpErrors errorMode="Custom" existingResponse="Auto">
That should fix it, its basically saying that "I dont want IIS Express to override the configuration of my site".
Thanks
Atif
# re: IIS 7 Error Pages taking over 500 Errors
By default httpErrors at the machine level hase overrideModeDefault set to Deny!
Why? Why? Why is this a default?
Anyway, by either editing applicationHost.config setting httpErrors overrideModeDefault to allow or using the FeatureDelegation in IISManager, setting to allow, everything springs into life!
James
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
Application_BeginRequest/EndRequest should work however, and I'm pretty sure that it does work there as I have code in there in most of my apps and I see errors from there. You sure that it's not working from those events?
# re: IIS 7 Error Pages taking over 500 Errors
To answer your question, I believe it’s not working..
At its simplest form this is what I did (to avoid custom IIS handlers or modules… Straight stock asp.net and mvc.): Create a stock VS 2010 MVC 3 web application and accept IIS default settings for running local IIS. I added the basic <customError> and <htmlErrors> settings for handling 404 and 500 errors in the web.config to target a specific static Error.HTML page relevant to the root of the web application. I put the following code in the application_start, application_beginrequest and application_endrequest.
//syntax is probably wrong. try{ throw new exception(); } catch (exception ex){ Server.ClearError(); Response.TrySkipIisCustomErrors = true; Response.StatusCode = 500; // target response code setup begin/end request and start app fails throw ex; // comment this out w/ different results in integrated mode. }
As you mentioned (while in integrated mode) the application_start behavior acted as you might expect because the application isn’t configured yet.
However while still in integrated mode throwing an exception in the begin or end request will not render my static error page defined in my web.conf. This will work fine while running in classic mode (Static page will render as I would expect).
This is driving me f’n nuts any insight would be appreciated.
# re: IIS 7 Error Pages taking over 500 Errors
What is the CallbackException in your posted code above?
Is it your own class? If it is, would you mind sharing the code, if that is possible.
Regards,
OutOfTouch
# re: IIS 7 Error Pages taking over 500 Errors
# re: IIS 7 Error Pages taking over 500 Errors
Background:
I support a website which has active content supplied by a back-end platform which is accessed via "Classic" ASP (and no- it doesn't support ASP.net as yet). Essentially, there's a COM object called from an ASP page that produces the page content. Because the syntax of this is hideously ugly, and for a couple of other reasons, it sits behind a set of rewrite rules using Helicon's ISAPI_rewrite.
Because the site-owner wants to know what's being searched for and not found, there's a catch all rule at the end of the rewrite rules, that "catches" any potential 404 and fires it to
default.asp?category=mar-runs&service=custom404&oqs={original query string}
The last act of this default.asp, *if* the service is "custom404" is to set the response code to 404. Which in turn triggers the 404 custom page and shows a nice site-formatted page to the end user.
This is a bit convoluted, but I couldn't find another way to both capture what was being sought *and* return a proper 404 (rather than a "soft" 404. which Google hates).
I also have some rewrite rules that create a 410 "gone" status- this is desired to be more forceful than 404; this page has been deliberately removed, it's not coming back so don't even ask!
I trigger this by rewriting the gone pages to "/page.gone" with a rewrite rule and appending various info into a query string - there's then a subsequent rule that returns a 410 response to "page.gone" - which goes to custom410.asp and shows a nicely-formatted page.
This all worked nicely (if a little kludgily) until today, when for some reason the 404 is being supplanted by a 500.19 status.
The detailed status says:
Error Code 0x800700b7 Config Error Cannot add duplicate collection entry of type 'error' with combined key attributes 'statusCode, subStatusCode' respectively set to '404, -1'
- which looks as though it's considering 410 to be part of 404.
The section of the config is:
<httpErrors> <error statusCode="404" path="/custom404.asp" responseMode="ExecuteURL" /> <error statusCode="410" path="/custom410.asp" responseMode="ExecuteURL" /> </httpErrors>
Which looks fine to me. Am I missing something subtle, here?
If there's a better method, I'm keen to hear it. The existingResponse="PassThrough" looks promising: does that mean I can set the status *and* the page content in my default.asp and it will just send that error status without using the standard error page?
If so, that's probably a better solution for me, as it keeps it all inside my platform.
# re: IIS 7 Error Pages taking over 500 Errors
But it is not working for me.
# re: IIS 7 Error Pages taking over 500 Errors