views:

86

answers:

3

My question is closely related to this question.

Here is a quick synopsis: My app is running in Classic Mode. I have the following code in Global.asax

    protected void Application_Error(Object sender, EventArgs e)
    {
        // ... boring stuff...
        HttpContext.Current.Server.Transfer("~/MyErrorPage.aspx", true);
    }

Everything works OK (i.e. I see MyErrorPage.aspx) when an error occurs if

<httpErrors errorMode="Detailed" />

but when errorMode="Custom" (or errorMode="DetailedLocalOnly" and the request is from a remote machine), I see the IIS custom error page followed by my error page (MyErrorPage.aspx).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt; 
<html xmlns="http://www.w3.org/1999/xhtml"&gt; 
    <!-- The contents of the default 500 page as configured in IIS 
         Which for me is the default %SystemDrive%\inetpub\custerr\<LANGUAGE-TAG>\500.htm
    -->
</html> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt; 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt; 

<html xmlns="http://www.w3.org/1999/xhtml" > 
    <!-- The contents of MyErrorPage.aspx -->
</html>

If I remove the default 500 error page from the IIS error pages section then I get the following output (note that I get "The page cannot be displayed..." instead of the custom 500 page)

The page cannot be displayed because an internal server error has occurred.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt; 

<html xmlns="http://www.w3.org/1999/xhtml" > 
    <!-- The contents of MyErrorPage.aspx -->
</html>

If I step through the code using the debugger, then I see that the IIS 500 page gets flushed to the client when I step over the Server.Transfer statement.
My error page then gets sent to the browser after the normal page lifecycle of the MyErrorPage.aspx (as you would expect). I have tried (in desperation) clearing the content of the response (HttpContext.Current.Response.Clear()) prior to the Server.Transfer but that has no effect. I have also tried calling Server.ClearError() prior to the transfer but that has no effect either.

Now as per the linked question, the "fix" is to set errormode="Detailed" but I do not want to see the detailed error pages for errors that are not handled in ASP.Net - for example I would prefer to see the IIS custom 404 page instead of the detailed page if I enter the url myApp/DoesNotExist.html. [Some of our customers absolutely insist that users never see the detailed error page because it is deemed a potential security weakness.]

Another "fix" is to redirect instead doing a server transfer but I would prefer to do a transfer if possible: doing a transfer means that the browser's URL is unchanged, meaning that, for example, if the error occurred because the app.was just starting up, they can hit F5 to retry the request. Redirecting obviously changes the browser's address to the error page.

Does anyone have an explaination as to why I am seeing this behaviour? Does anyone have a solution?

Thanks in advance.

Edit

I have knocked together a little app that demonstrates the behaviour :

http://rapidshare.com/files/427244682/Err.zip

[Make sure that the app is running in Classic mode.]

If you click on a link that has both transfer and setstatus args set then you see the problem. BANG

A: 

Add the following attribute to your <httpErrors> tag:

existingResponse="PassThrough"

and that should fix it. PassThrough is the default when errorMode="Detailed".

More info at http://blogs.iis.net/ksingla/archive/2008/02/18/what-to-expect-from-iis7-custom-error-module.aspx

Gonzalo
I have tried this setting and it does allieviate the problem of seeing 2 error pages but in the case of going to http://host/app/doesnotexist.html (and other such errors that I would expect to be handled by IIS) I get nothing and the browser comes up with a "oops something is broken" error.
Chris F
For that, I think you also need to set defaultPath="~/MyErrorPage.aspx" in the httpErrors tag.
Gonzalo
Ah - I will give that a whirl. But, even if that means I get some error page instead of blank, I will have to live with the same page for 404, 401, 500 errors (well the ones that are not spawned from my code at least).
Chris F
You can Server.Transfer() from your error page too based on the Status code, or inspecting context.Errors, or...
Gonzalo
A: 

There's a property on Response Response.TrySkipIisCustomErrors - which is apparently supposed to prevent IIS from injecting it's own error page code. Presumably you're able to set that to true as the error happens somehow so you don't get the custom error pages for actual errors, but 404's remain with IIS.

Paul
Did you managed to give this a go? I haven't tried it yet
Paul
TrySkipIisCustomErrors is already true by default (because my app is running in classic mode). I have tried settings anyway and it has no effect on the behaviour. :(
Chris F
D'oh! How annoying
Paul
A: 

If your custom error page logic is based only on the error number, than you should define the custom error pages in the web.config. And set the status code in your generic error page based on the status code from Server.GetLastError(). If you specify defaultResponseMode="ExecuteURL" than you'll have the URL of the requsted page in the client's browser.

<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="~/ErrorPages/CustomErrorPage.aspx" redirectMode="ResponseRewrite">
      <error statusCode="404" redirect="~/ErrorPages/404.aspx"/>
      <error statusCode="500" redirect ="~/ErrorPages/500.aspx"/>
</customErrors>
</system.web>

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace" defaultPath="~/ErrorPages/CustomErrorPage.aspx" defaultResponseMode="ExecuteURL">
      <error statusCode="404" path="~/ErrorPages/404.aspx"/>
      <error statusCode="500" path ="~/ErrorPages/500.aspx"/>
    </httpErrors>
</system.webServer>

And put this in CustomErrorPage's Page_Load

    Exception ex = Server.GetLastError();
    if (ex is HttpException){
        Response.StatusCode = ((HttpException)ex).GetHttpCode();
    }

This way you've elliminated the need to Server.Transfer (it is done implicitly by the enviromnet) and you have a generic page for the errors that are not handled explicitly.

jaraics
Thank you for the response. Unfortunately several different errors (which each need their own error page) can generate a 500 error.
Chris F
In that case you can try to Server.Transfer from your 500.aspx (move the logic from global.asax to 500.aspx) see if that works? (I couldn't run your attached sample, as I'm constantly getting an 500, no matter what page I try to load.)
jaraics