Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
            }
        }
    }
}
like image 438
Kaos Avatar asked Apr 14 '11 14:04

Kaos


3 Answers

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.

like image 130
Kev Avatar answered Oct 26 '22 23:10

Kev


ASP.NET is never being invoked by IIS. IIS handles the page request, sees that the page doesn't exist, and ASP.NET never gets loaded.

If you want ASP.NET to get loaded regardless of whether the file exists, you need to change your IIS configuration. Are you using IIS6 or IIS7/7.5?

like image 40
Katie Kilian Avatar answered Oct 27 '22 01:10

Katie Kilian


You need to set up wildcard mapping so that all requests go through .Net

if your using IIS6 the following link should help you out:

http://professionalaspnet.com/archive/2007/07/27/Configure-IIS-for-Wildcard-Extensions-in-ASP.NET.aspx

Wildcard script mapping and IIS 7 integrated pipeline:

http://learn.iis.net/page.aspx/508/wildcard-script-mapping-and-iis-7-integrated-pipeline/

like image 35
RandomWebGuy Avatar answered Oct 27 '22 01:10

RandomWebGuy