Implementing a custom error in global.asax - c#

In my global.asax file, I have the following code:
void Application_Error(object sender, EventArgs e)
{
Exception TheError = Server.GetLastError();
if (TheError is HttpException && ((HttpException)TheError).GetHttpCode() == 404)
{
Response.Redirect("~/404.aspx");
}
else
{
Response.Redirect("~/500.aspx");
}
}
When I navigate to an non-existing page, I get the generic error page. I don't want anything pertaining to custom error in the web.config because my plan is to add code to the global.asax file to log the exceptions. Is there a way to handle custom error with just the global.asax file?

EDIT:
The answer is not obvious, but this seems to explain it: http://www.asp.net/web-forms/tutorials/deployment/deploying-web-site-projects/displaying-a-custom-error-page-cs
If you scroll down a ways, you'll find this lovely tidbit:
Note: The custom error page is only displayed when a request is made
to a resource handled by the ASP.NET engine. As we discussed in the
Core Differences Between IIS and the ASP.NET Development Server
tutorial , the web server may handle certain requests itself. By
default, the IIS web server processes requests for static content like
images and HTML files without invoking the ASP.NET engine.
Consequently, if the user requests a non-existent image file they will
get back IIS's default 404 error message rather than ASP.NET's
configured error page.
I've emphasized the first line. I tested this out in the default template, and sure enough, this URL:
http://localhost:49320/fkljflkfjelk
gets you the default IIS page, whereas simply appending a .aspx makes the Application_Error kick in. So, it sounds like you need to enable customErrors/httpErrors if you want to have all errors handled.
For IIS <= 6, add to <system.web>:
<customErrors mode="On" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="/404.aspx"/>
<error statusCode="500" redirect="/500.aspx"/>
</customErrors>
For IIS7+, add to <system.webServer>:
<httpErrors errorMode="Custom">
<remove statusCode="404"/>
<error statusCode="404" path="/404.aspx" responseMode="ExecuteURL"/>
<remove statusCode="500"/>
<error statusCode="500" path="/500.aspx" responseMode="ExecuteURL"/>
</httpErrors>
I'll leave the original answer in case someone finds it useful.
I believe you need to clear the existing error code from the response, otherwise IIS ignores your redirect in favor of handling the error. There's also a Boolean flag, TrySkipIisCustomErrors, you can set for newer versions of IIS (7+).
So, something like this:
void Application_Error(object sender, EventArgs e)
{
Exception TheError = Server.GetLastError();
Server.ClearError();
// Avoid IIS7 getting in the middle
Response.TrySkipIisCustomErrors = true;
if (TheError is HttpException && ((HttpException)TheError).GetHttpCode() == 404)
{
Response.Redirect("~/404.aspx");
}
else
{
Response.Redirect("~/500.aspx");
}
}

Looks like IIS is looking for a page and did not find it.
Make sure:
404.aspx is created
customErrors tag from web.config is not present.

Related

Where EXACTLY Do I Catch 404 And 500 Errors?

I know of a couple ways to catch 404 and 500 errors. For example, I can use web.config, route.config, global.asax, and a controller; however, I don't where to put the code to make it catch these errors. Let me tell you what I've done.
Route Config:
The route.config works for 404 errors, but it won't work for 500 errors (to my knowledge). Regardless, I DON'T want to use it because I've heard that it has some downsides to it. Also, it seems to be a very poor solution to this problem IMO.
web/Web Config:
I have tried every possible web config file I have: web.config (system.web and system.webServer) and Web.config (system.web and system.webServer) as well as the Web Debug File (system.web and system.webServer). ALL of these didn't catch the error (It frustrates me because no one will tell me where EXACTLY to put the code so it catches. i.e., place1 -> place2 -> place3 -> etc... Every answer gives me the code and either says Web.config or web.config - I know that but WHERE in Web.config or web.config?) I heard this way has restrictions, but they aren't relevant to me. I know it only works for IIS 7+. I think my team and I are using IIS 8, so that shouldn't be a problem for me. I prefer a method in global.asax or web.config/Web.config.
Global Asax:
I am using the two application error handler methods Application_EndRequest and Application_Error, both of which aren't working. The only error I get is a 200 error (this error is only caught by EndRequest), which isn't an error but quite the opposite. I don't know why I would be getting this when my page shows a 404 error. To my knowledge, it's because Global Asax isn't programmed to catch these errors, but web.config and/or Web.config, and as you know, I don't know how to make that work. I will accept a global.asax solution because I haven't heard anything bad about it, yet.
Controller:
I only tried one solution: protected override void HandleUnknownAction(string actionName), and the solution called for a route in route.config {*url}. Surprise, surprise, this didn't work either (as expected). I learned that this solution using the code {*url} to find 404 errors (or at least it was the solution I searched.) And as I said earlier, I don't want a route.config solution. If I am correct, protected override void HandleUnknownAction(string actionName) may work in global but not for me.
Tried Solutions:
protected override void HandleUnknownAction(string actionName)
protected void Application_EndRequest()
protected void Application_Error(object sender, EventArgs e)
protected override void OnException(ExceptionContext filterContext)
5.
<system.webServer>
<httpErrors errorMode="Custom" defaultResponseMode="File" >
<remove statusCode="404" />
<remove statusCode="500" />
<error statusCode="404"
path="404.html" />
<error statusCode="500"
path="500.html" />
</httpErrors>
</system.webServer>
6.
<httpErrors>
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath=""
path="http://yoursite.com/index.asp?syserror" responseMode="Redirect" />
</httpErrors>
7.
<customErrors mode="On">
<error statusCode="404" redirect="/Custom404.html" />
<error statusCode="500" redirect="/Custom500.html" />
</customErrors>
8.
routes.MapRoute(
"Error", // Route name
"Error/{errorCode}", // URL with parameters
new { controller = "Page", action = "Error", errorCode= UrlParameter.Optional }
);
9.
if (Context.Response.StatusCode == 404)
{
Response.Clear();
var rd = new RouteData();
rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
rd.Values["controller"] = "Errors";
rd.Values["action"] = "NotFound";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
10.
Exception exception = Server.GetLastError();
// Log the exception.
ILogger logger = Container.Resolve<ILogger>();
logger.Error(exception);
Response.Clear();
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
if (httpException == null)
{
routeData.Values.Add("action", "Index");
}
else //It's an Http Exception, Let's handle it.
{
switch (httpException.GetHttpCode())
{
case 404:
// Page not found.
routeData.Values.Add("action", "HttpError404");
break;
case 500:
// Server error.
routeData.Values.Add("action", "HttpError500");
break;
// Here you can handle Views to other error codes.
// I choose a General error template
default:
routeData.Values.Add("action", "General");
break;
}
}
Note: Firstly, I may have adjusted some of this solutions to fit my code, Secondly, I take no credit for this solutions. I found most, if not all, of the solutions on other forum pages. Thirdly, the only solution that worked is the 8th one; however, I believe it only works for 404s. Nevertheless, I don't want to use it because I believe it is a bad solution (correct me if I'm wrong.)
Conclusion:
I am NOT asking you to solve the solution for me. I simply need two thing: one, I need to be corrected if I was misinformed (through a comment or answer); and two, I need to know WHERE to put the code and the result of the code (through either a picture or an explanation.) If you put something like the following:
Here is code that worked for me
[Insert Code Here]
[Insert more description]
I will most likely copy the code, change it, try it, and inevitably get upset if/when it fails. If you could take the time to explain how 404 errors are caught and a global.asax or web/Web Config Solution, I will greatly appreciate it. I have been struggling with this problem for a while now, and I have put in a lot of time and effort into it, only to get vague solutions with little to no explanation as to why it catches 404/500 errors or where to put it, exactly.
Edit:
Here are the error methods I want to hit. I can assure you that they are routed in my route.config using routes.MapMvcAttributeRoutes();
[Route("~/page_not_found")]
public ActionResult PageNotFound()
{
Response.StatusCode = 404;
return View();
}
[Route("~/internal_server_error")]
public ActionResult InternalServerError()
{
Response.StatusCode = 500;
return View();
}
my experience was in this direction.
In terms of user experience, 500 redirects are made so that he does not see the wrong page. but redirecting 500 does not send exception parameters to the redirected page.
To log the error with 500, it is necessary to use global.asax.

customErrors vs Custom modules

I've currently got httpErrors setup to deal with 500's here:-
<httpErrors errorMode="Custom" existingResponse="Replace">
......
<remove statusCode="500"/>
<error statusCode="500" path="/server-error" responseMode="ExecuteURL"/>
</httpErrors>
This works fine, but in the case where iis receives the error I still get the yellow screen of death. An example is when entity framework can not connect to the database and I receive:-
Cannot open database "Test-DB" requested by the login. The login failed. Login failed for user 'sa'.
I've setup customErrors to deal with this:-
<customErrors mode="On" defaultRedirect="error.html" redirectMode="ResponseRedirect">
<error statusCode="500" redirect="error.html" />
</customErrors>
which works as expected as long as there is no modules without preCondition="managedHandler".
I have a few modules which deal with images and css files and are in the same project.
<modules runAllManagedModulesForAllRequests="false">
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
<add name="ImageHandler" type="foo.bar.ProductImageHandlerHttpModule" />
<add name="CustomCssHandler" type="foo.bar.CustomCssHttpModule" />
<add name="Glimpse" type="Glimpse.AspNet.HttpModule, Glimpse.AspNet" preCondition="integratedMode" />
</modules>
Comment these out and I get the error.html, keep them in and I get
Runtime Error Description: An exception occurred while processing your
request. Additionally, another exception occurred while executing the
custom error page for the first exception. The request has been
terminated.
showing that a module from the project is also erroring when trying to show the error.html.
Does anyone know a fix/workaround?
Its a tough situation - your error comes from HttpModule, which is run for every single request - including request to error.html page. One way is to route static files (such as error.html) just via your server - and ignore them on .NET level; this may not be always possible (sometimes handling static files on .NET level is handy). The only other way I can think off is hook in global.asax to error event and handle it yourself (which will ignore the customErrors)
Something like:
public class Global : System.Web.HttpApplication
{
protected void Application_Error(object sender, EventArgs e)
{
// is there an error ?
var error = Server.GetLastError();
if (error != null)
{
// mark the error as - "i'll handle it myself"
Server.ClearError();
// load error.html manually & dump it to response
var content = File.ReadAllText(Server.MapPath("~/error.html"));
Context.Response.Write(content);
// set correct error code
Context.Response.StatusCode = 500;
}
}
}
Note: this can be ironed out, but you see the general principle ...
As #Ondrej said, another error occurred when handling the custom error page comes from unhandled exception on HttpModule, thus it is necessary to either skip or bypassing customErrors section on web.config file.
This code also includes how to skip custom errors generated from IIS, besides generating custom HTML error page:
protected void Application_Error(Object sender, EventArgs e)
{
// Get last error occurred
var exception = Server.GetLastError();
// catch unhandled exceptions
if (exception is HttpUnhandledException)
{
// handle the error by ASP .NET itself
Server.ClearError();
// write status code handling
HttpContext.Current.Response.WriteFile(Server.MapPath("~/error.html"));
HttpContext.Current.Response.StatusCode = 500;
HttpContext.Current.Response.StatusDescription = "Internal Server Error";
// set ASP .NET handlers instead of using IIS handlers
HttpContext.Current.Response.TrySkipIisCustomErrors = true;
}
}
Additionally, to get rid of YSOD pages generated by IIS you may set existingResponse attribute to pass error handling into ASP .NET Application_Error method as this:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="PassThrough">
<remove statusCode="500" />
<error statusCode="500" responseMode="File" path="/error.html" />
</httpErrors>
</system.webServer>
The reason behind those settings based from Kanwaljeet Singla's explanation (source here):
existingResponse
Value of this section level property tells custom error module what to
do when response text is not blank. If a module call
IHttpResponse::SetStatus to set an error code and also sets up
response text, existingResponse property tells if custom error module
should replace current response text with its own error text or should
it let the current response text pass through. Asp.Net and WCF are
example of modules which sets error response text. This property can
be set to following three values.
Replace – This value make custom error module to always replace the error information with text generated by custom error module. If
existingResponse is set to “Replace”, errors/exceptions generated by
Asp.Net/WCF are replaced by IIS7 errors.
PassThrough – If existingResponse is seen as “PassThrough”, custom error module will always pass through responses from modules.
This setting will make custom error module return blank response if
modules don’t set any text.
Auto – This is the default value and tells custom error module to do the right thing. Actual error text seen by clients will be
affected depending on value of fTrySkipCustomErrors returned in
IHttpResponse::GetStatus call. When TrySkipCustomErrors is set to
true, custom error module will let the response pass through but if it
is set to false, custom errors module replaces text with its own text.
Asp.Net/WCF call IHttpResponse::SetStatus with TrySkipCustomErrors
true so that IIS doesn’t override their errors with its own. When
effective errorMode is “Detailed” and response is non-empty, this
value of existingResponse will act as “PassThrough” regardless of
value of TrySkipCustomErrors.
Hopefully this may figure out the essential things when setting custom error page for unhandled exception responses.
MSDN References:
https://msdn.microsoft.com/en-us/library/ms690576(v=vs.90).aspx
https://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors(v=vs.110).aspx
SO References:
IIS7 Overrides customErrors when setting Response.StatusCode?
How to send status code "500" for generic error page in IIS?
Getting IIS7 'one liner' error when using custom error settings in web.config
It seems if your iis is set with Integrated Mode then Modules will still be run for static files. If you set it to Classic mode it will ignore the managed modules for static files.
Please refer this thread for more infomration see the answer of João Angelo Requests for static files are hitting the managed code in ASP.NET MVC3
The fix is either you changed your handler to not throw exception or you change iis to classic mode so if any handler or other part throws exception and iis redirects to error.html then none of manage module should hit.

asp.net SEO friendly 404 set redirectMode in code

I am trying to achive the below configuration through code
<customErrors defaultRedirect="GenericError.htm" mode="On"
redirectMode="ResponseRewrite">
<error statusCode="404" redirect="404.aspx"/>
</customErrors>
SEO - Handle 404 errors
if ((ex is HttpException && 404 == ((HttpException)ex).GetHttpCode()))
{
HttpContext.Current.RewritePath("~/404.aspx");
//Response.Redirect("~/404.aspx");
//Server.Transfer("~/404.aspx"); //Error executing child request for
}
Question:
How can I set redirectMode="ResponseRewrite" in programattically here?
I dont want to change the url.
Calling Response.Redirect changes the url to /404.aspx and uses 302 Http Status code.
I set Response.StatusCode=404 in page_load of 404.aspx.
Followed http://weblogs.asp.net/paxer/archive/2010/05/31/asp-net-http-404-and-seo.aspx to implement SEO friendly 404. But via code
Also HttpContext.Current.RewritePath not at all working
Server.Transfer has some errors in Application_Error, check https://stackoverflow.com/a/13344446/1909604

ASP.NET 404 httpmodule

Im using a CMS product called EPiServer. We need to create our own method of displaying 404's which just can't be achieved using .NET's standard customErrors. We've writen a module which we use to check for the HttpStatusCode. We do this in the EndRequest method.
If the status is 404, we query EPiServer for the appropriate 404 page, and then Transfer the request over to that page. However this doesnt return a 404, and even if I do the following the correct status isnt returned:
HttpContext.Current.Response.StatusCode = 404;
HttpContext.Current.Response.StatusDescription = "Page not Found";
HttpContext.Current.Server.TransferRequest(newPage);
Likewise, if I do a response.redirect instead of a TransferRequest then its not a proper 404 because the url has then changed...
Whats the right way of doing this?
Thanks in advance
Al
Not a direct answer to your question but could also take a look at this open source 404 handler: https://www.coderesort.com/p/epicode/wiki/404Handler
It is also available on episervers nuget feed
Which IIS version are you using? For IIS7 or 7.5 you might need something like this:
<httpErrors errorMode="Custom">
<remove statusCode="404" />
<error statusCode="404" path="/somenotfoundpage.aspx" responseMode="ExecuteURL" />
<remove statusCode="500" />
<error statusCode="500" path="/someerrorpage.aspx" responseMode="ExecuteURL" />
</httpErrors>
You should set the status code in the codebehind of the template you're using for your 404 page.
If this is a plain content page, either create a new template or add a Status Code property to the page and logic in the code behind to send the appropriate header if this is not null or empty.
Try setting the status code on the page that you transfer to - in your error page template. I'm not sure that having a separate module is necessary - you can simply handle the HttpApplication's Error event in Global.asax.cs.
Thanks for your responses.
I actually got this working by doing the following:
In the EndRequest event handler I transferred the request off to the correct EPiServer page, and included a querystring param in the call i.e.
app.Context.Server.TransferRequest(pageUrl + "&internal=true");
Then in the PostRequestHandlerExecute event I check for the querystring param, if it exists it can only be because it's a 404 so I return the correct status:
HttpContext.Current.Response.StatusCode = 404;
HttpContext.Current.Response.StatusDescription = "Page not Found";
Works like a charm.
Thanks
higgsy

Custom HTTP error page

In asp.net, I can define a custom error page like this:
<configuration>
<system.web>
<customErrors mode="On">
<error statusCode="404" redirect="/servererrors/404.aspx" />
</customErrors>
</system.web>
</configuration>
Now my question: If I replace, say 404.aspx with AnyHTTP.aspx,
and want to get the number of the http error to generalize the page, how do I get that error numer?
Try this setting in CustomErrors (ASP.NET 3.5 SP1):
<customErrors mode="RemoteOnly" defaultRedirect="/servererrors/AnyHTTP.aspx" RedirectMode="ResponseRewrite"/>
As a different solution, you can also do this in Global.asax:
void Application_Error(object sender, EventArgs e)
{
Server.Transfer("/servererrors/AnyHTTP.aspx");
}
and on your error page, load the last error:
Exception e = Server.GetLastError();
It is important to use Server.Transfer() in the Global.asax file; using Response.Redirect will throw a 302 error and you will lose the error that you wanted to catch.
Well you might take a look at http://www.raboof.com/projects/Elmah/ before you venture to deep into doing your own thing...
I'd recommend not using the web.config method. customErrors redirects to the error page, which makes little sense. Essentially it first says "oh yes, that'll work perfectly, you just need to go here instead", and then says "oh, we didn't find that". That's really a bug (if there isn't anything here, then why did the server tell me to go here, clearly to the user code it looks like you the server code messed up; they went to the right URI and then you directed them to the wrong one).
Use Server.Transfer() from global.asax, set a default HTTPHandler, or set IIS to execute (not redirect to) your .aspx or other file with your implementation. If you want the same handler to manage each error, then you could, for example, do a Server.Transfer() from global.asax, but include a query string parameter about the type of error (whether simply an HTTP status code, or something more detailed), or pass information in the HttpContext.

Categories

Resources