Kaos Kaos - 2 months ago 12
ASP.NET (C#) Question

Custom Error Handling in web.config / Global.asax not handling non-existant directory

Question is: Why is custom Error handling not working for non-existing paths/directories?

Updated with fixed code (thanks to everyone for you input):

* Updated code for web.config and global.asax *

<httpErrors errorMode="Custom">
<remove statusCode="500" subStatusCode="-1" />
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" subStatusCode="-1" prefixLanguageFilePath="" path="/*****.aspx" responseMode="ExecuteURL" />
<error statusCode="500" subStatusCode="-1" prefixLanguageFilePath="" path="/*****.aspx" responseMode="ExecuteURL"/>
</httpErrors>

added this to the global.asax to stop IIS from handling my 500 errors
after @Kev's suggestions IIS handled my 500's these lines fixed that
HttpApplication myApplication = new HttpApplication();
myApplication.Response.TrySkipIisCustomErrors = true;


We have a site setup with custom error handling in
web.config
and
global.asax
(the setup is shown below). We are able to handle all 404's and 500's with no problems. The errors are caught in the
Application_Error
in the
global.asax
, logged to a DB then using
HttpContext
we set the status code and use
Server.Transfer()
to move the user to the appropriate error page (redirects cause a 302, and hurts SEO).

The problem is when the user types in
http://www.example.com/whatever
a blank page is shown in Firefox and in IE it shows the IE 404 page. Firebug shows no status codes being fired, and when I debug the solution, no breakpoints I have set are hit in the
global.asax
. The odd thing is that a user can enter
http://www.example.com/whatever/hmm.aspx
and an error will be hit. It seems it's only working on non-existing pages and not paths/directories that don't exist.

Below is my
web.config
code for the Errors and my
global.asax
code for Application error.

I've added the ** to hide info, they have valid
.aspx
pages in them:

Web config:

<customErrors defaultRedirect="~/******.aspx" mode="On"
redirectMode="ResponseRewrite">
<error statusCode="500" redirect="~/*****.aspx" />
<error statusCode="404" redirect="~/*****.aspx" />
</customErrors>

<httpErrors errorMode="Custom">
<remove statusCode="500" subStatusCode="-1" />
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" subStatusCode="-1" prefixLanguageFilePath="" path="/*****.aspx" responseMode="ExecuteURL" />
<error statusCode="500" subStatusCode="-1" prefixLanguageFilePath="" path="/*****.aspx" responseMode="ExecuteURL"/>
</httpErrors>


Code:

protected void Application_Error(Object sender, EventArgs e)
{
// At this point we have information about the error
HttpContext ctx = HttpContext.Current;

// set the exception to the Context
Exception exception = ctx.Server.GetLastError();

// get the status code of the Error
int httpCode = ((HttpException)exception).GetHttpCode();

// get the IP Address
String strHostName = string.Empty;
String ipAddress_s = string.Empty;
strHostName = System.Net.Dns.GetHostName();

System.Net.IPHostEntry ipEntry = System.Net.Dns.GetHostByName(strHostName);
System.Net.IPAddress[] addr = ipEntry.AddressList;

for (int i = 0; i < addr.Length; i++)
{
ipAddress_s += "IP Address {" + (i + 1) + "} " +
addr[i].ToString() + Environment.NewLine;
}

// setup the error info one for user display and one for the DB Insert
string errorInfo =
"<br /><b>Error Location:</b> " + ctx.Request.Url.ToString() +
"<br /><br /><b>Error Source:</b> " + exception.Source +
"<br /><br /><b>Error Try/Catch:</b> " + exception.InnerException +
"<br /><br /><b>Error Info:</b> " + exception.Message +
"<br /><br /><b>Status Code:</b> " + httpCode +
"<br /><br /><b>Stack trace:</b> " + exception.StackTrace;
string errorInfoDB =
"||Error Location: " + ctx.Request.Url.ToString() +
"||Error Source: " + exception.Source +
"||Error Try/Catch: " + exception.InnerException +
"||Error Info: " + exception.Message +
"||HttpErrorCode: " + httpCode +
"||Stack trace: " + exception.StackTrace +
"||IP Address: " + ipAddress_s;

// clean the input befor you put it in the DB
char quote = (char)34;
char filler = (char)124;
char tick = (char)39;
char greaterThan = (char)60;
char lessThan = (char)62;
errorInfo = errorInfo.Replace(quote, filler);
errorInfo = errorInfo.Replace(tick, filler);
errorInfo = errorInfo.Replace(greaterThan, filler);
errorInfo = errorInfo.Replace(lessThan, filler);

errorInfoDB = errorInfoDB.Replace(quote, filler);
errorInfoDB = errorInfoDB.Replace(tick, filler);
errorInfoDB = errorInfoDB.Replace(greaterThan, filler);
errorInfoDB = errorInfoDB.Replace(lessThan, filler);

string pattern = string.Empty;
string replacement = "sQueEl";
pattern = "/cookie|SELECT|UPDATE|INSERT|INTO|DELETE|FROM|NOT IN|WHERE|TABLE|DROP|script*/ig";
errorInfoDB = Regex.Replace(errorInfoDB, pattern, replacement);

pattern = "/cookie|select|update|insert|into|delete|from|not in|where|table|drop|script*/ig";
errorInfoDB = Regex.Replace(errorInfoDB, pattern, replacement);


if (httpCode == 404)
{
InSert_To_DB_Class(*****, *****, *****, *****, *****, errorInfoDB);
}
else
{
InSert_To_DB_Class(*****, *****, *****, *****, *****, errorInfoDB);
}

// set the error info to the session variable to display to the allowed users
Application["AppError"] = errorInfo;

// clear the error now that is has been stored to a session
ctx.Server.ClearError();
ctx.Response.ClearHeaders();
// set the status code so we can return it for SEO
ctx.Response.StatusCode = httpCode;
ctx.Response.TrySkipIisCustomErrors = true;
HttpApplication myApplication = new HttpApplication();
myApplication.Response.TrySkipIisCustomErrors = true;
try
{
if (ctx.Request.RawUrl.Contains("/*****"))
{
// redirect to the error page
ctx.Server.Transfer("~/*****.aspx", false);
}
else if(ctx.Request.RawUrl.Contains("/*****"))
{
ctx.Server.Transfer("~/*****/*****.aspx", false);
}
else
{
// check the httpCode
if (httpCode == 404)
{
// set the page name they were trying to find to a session variable
// this will be cleared in the ****** page
Application["404_page"] = exception.Message;
// redirect to the 404 page
ctx.Server.Transfer("~/*****", false);
}
else
{
// redirect to the error page
ctx.Server.Transfer("~/*****", false);
}
}
}
}

Kev Kev
Answer

From this comment to Mr. Disappointment:

Thanks, I'm using IIS 7 local and IIS 7.5 on live. Let me know when you find the material.

If your application is running in an application pool configured to run in Classic Pipeline mode then content not intended for ASP.NET won't hit the ASP.NET runtime. i.e. folders that don't exist. These will be handled directly by IIS.

You have a couple choices:

  1. Set the application pool to Integrated Pipeline mode. You may also need to configure the following setting if IIS's error handling "eats" your ASP.NET 404 and 500 status code from ASP.NET:

    <configuration>
      <system.webServer>
        <httpErrors existingResponse="PassThrough" />
      </system.webServer>
    </configuration>
    
  2. If the application is not well behaved in "Integrated Pipeline" mode but you're just concerned about getting a page displayed for a 404 response then configure the following:

    <system.webServer>
      <httpErrors>
        <remove statusCode="404" subStatusCode="-1" />
        <error statusCode="404" prefixLanguageFilePath="" 
               path="/404.aspx" responseMode="ExecuteURL" />
      </httpErrors>
    </system.webServer>
    

    You'd need to set the 404 status code in the page, otherwise it'll just return a 200.

    If you use a static page, for example:

    <error statusCode="404" prefixLanguageFilePath="" 
           path="404.html" responseMode="File" />
    

    This will return a 404.

  3. If the application is not well behaved in "Integrated Pipeline" mode AND you absolutely must pass 404 errors through your error handler then you may have to manually map wildcard content to the ASP.NET HttpHandler so that such requests do hit the pipeline. This would be a sub-optimal solution.

Comments