I have implemented Custom Error Functionality in my project, and its working on local IIS but not working on live server. I have implemented this functionality using Global.asax file and i am calling my custom error action method in my custom error controller in MVC. I have published and run on local IIS and its work well,but on live server.
my Global.asax.cs file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//do not register HandleErrorAttribute. use classic error handling mode
filters.Add(new HandleErrorAttribute());
}
protected void Application_Error(Object sender, EventArgs e)
{
LogException(Server.GetLastError());
CustomErrorsSection customErrorsSection = (CustomErrorsSection)ConfigurationManager.GetSection("system.web/customErrors");
string defaultRedirect = customErrorsSection.DefaultRedirect;
if (customErrorsSection.Mod e== CustomErrorsMode.On)
{
var ex = Server.GetLastError().GetBaseException();
Server.ClearError();
var routeData = new RouteData();
routeData.Values.Add("controller", "Common");
routeData.Values.Add("action", "CustomError");
if (ex is HttpException)
{
var httpException = (HttpException)ex;
var code = httpException.GetHttpCode();
routeData.Values.Add("status", code);
}
else
{
routeData.Values.Add("status", 500);
}
routeData.Values.Add("error", ex);
IController errorController = new Test.Controllers.CommonController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
}
My Custom error Controller and its action method
public ActionResult CustomError(int status, Exception error)
{
var model = new CustomErrorModel();
model.Code = status;
model.Description = Convert.ToString(error);
Response.StatusCode = status;
return View(model);
}
So what should I do?
I had this problem where errors on live IIS server weren't showing custom error pages (which return proper HttpStatusCodes) but it WAS working on local IIS (localhost address using default website - not Cassini). They should have worked exactly the same I would have thought - anyway this web.config setting fixed it.
<configuration>
<system.webServer>
<httpErrors existingResponse="PassThrough"></httpErrors>
</system.webServer>
</configuration>
Note that my setup uses Application_Error in global.asax and just this other web.config setting:
<customErrors mode="On">
<!-- There is custom handling of errors in Global.asax -->
</customErrors>
2 approaches
Route Method
// We couldn't find a route to handle the request. Show the 404 page.
routes.MapRoute("Error", "{*url}", new { controller = "Error", action = "CustomError" } );
or
Custom Error Handler in Web.config:
<customErrors mode="On" >
<error statusCode="404" redirect="~/CatchallController/CustomError" />
</customErrors>
The condition raised by no route matching is a 404. This way you direct all non-match to your ~/CatchallController/CustomError
Related
I am having an issue where I want to display custom 404 and 500 views only for specific controllers.
The reason I want to do this is that we have APIControllers and standard Controllers in the same project and I do not want to rewrite the API error responses.
I am trying to achieve this with a custom attribute which inherits HandleErrorAttribute, but I cannot seem to get 404's to go through it. Here is what I have for OnException so far:
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
return;
//Defaults to 500 if it cannot be determined
int statusCode = new HttpException(null, filterContext.Exception).GetHttpCode();
//We only want to capture 500 and 404 at this stage
switch (statusCode)
{
case 404:
View = _404ViewName;
break;
case 500:
View = _500ViewName;
break;
default:
return;
}
Master = _layoutViewName;
string controllerName = (string)filterContext.RouteData.Values["controller"];
string actionName = (string)filterContext.RouteData.Values["action"];
HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
ViewDataDictionary viewData = new ViewDataDictionary<HandleErrorInfo>(model);
filterContext.Result = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = viewData,
TempData = filterContext.Controller.TempData
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
// Certain versions of IIS will sometimes use their own error page when
// they detect a server error. Setting this property indicates that we
// want it to try to render ASP.NET MVC's error page instead.
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
I have previously tried this in the config file:
<httpErrors errorMode="DetailedLocalOnly" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" responseMode="ExecuteURL" path="/Error/404"/>
<remove statusCode="500"/>
<error statusCode="500" responseMode="ExecuteURL" path="/Error/500"/>
</httpErrors>
But that rewrote the API error responses.
I currently only use the <customErrors mode="On" /> tag instead which works great for 500 errors but I get generic IIS errors for 404s using that.
If I add <httpErrors errorMode="Custom" existingResponse="PassThrough" /> into the web config, the 500 still gives me my custom message view, but the 404 now just shows a blank page.
What do I need to do in order to get 404s to go through my custom attribute as well?
Or if this is not possible, what is a different approach I can take that is not going to affect the API Controllers?
I implemented a custom error handler for my MVC5 project and everything would be fine if it wasn't of the customErrors attribute. I'll explain: When I got an error in the application, I catch it inside void Application_Error from Global.asax like this:
protected void Application_Error(object sender, EventArgs e)
{
var httpContext = ((HttpApplication)sender).Context;
ExecuteErrorController(httpContext, Server.GetLastError());
}
public static void ExecuteErrorController(HttpContext httpContext, Exception exception)
{
if (!exception.Message.Contains("NotFound") && !exception.Message.Contains("ServerError"))
{
var routeData = new RouteData();
routeData.Values["area"] = "Administration";
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Insert";
routeData.Values["exception"] = exception;
using (Controller controller = new ErrorController())
{
((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
}
}
}
Then, inside my ErrorController I do:
public ActionResult Insert(Exception exception)
{
ErrorSignal.FromCurrentContext().Raise(exception);
Server.ClearError();
Response.Clear();
switch (Tools.GetHttpCode(exception)) // (int)HttpStatusCode.NotFound;
{
case 400:
return RedirectToAction("BadRequest");
case 401:
return RedirectToAction("Unauthorized");
case 403:
return RedirectToAction("Forbidden");
case 404:
return RedirectToAction("NotFound");
case 500:
return RedirectToAction("ServerError");
default:
return RedirectToAction("DefaultError");
}
}
public ActionResult Unauthorized()
{
return View();
}
...
So the first time, everything works perfectly
But !! The code repeat itself because the NotFound or ServerError page aren't in the Shared folder. Those page are supposed to be set in customErrors attribute BUT the thing is I don't need it at all. I finally got this error: ERR_TOO_MANY_REDIRECTS because of that.
I read all day to find any answer about that, and it seams that everyone who published their code do the same kind of pattern as mine, and no matter what I tried, nothing works.
Notice my desperate if condition: if (!exception.Message.Contains("NotFound") && !exception.Message.Contains("ServerError"))
I also comment those two lines in the global.asax because everywhere I read, it says we need to remove them in order to get this done.
//GlobalConfiguration.Configure(WebApiConfig.Register);
//FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
Also, because of the desparate if, I got this answer:
Runtime Error
Description: An application error occurred on the server. The current custom error settings for this application prevent the details of the application error from being viewed.
Details: To enable the details of this specific error message to be viewable on the local server machine, please create a <customErrors> tag within a "web.config" configuration file located in the root directory of the current web application. This <customErrors> tag should then have its "mode" attribute set to "RemoteOnly". To enable the details to be viewable on remote machines, please set "mode" to "Off".
<!-- Web.Config Configuration File -->
<configuration>
<system.web>
<customErrors mode="RemoteOnly"/>
</system.web>
</configuration>
I also tried Response.TrySkipIisCustomErrors = true; and it doesn't work!
So, how can I get ride of customErrors completely and manage my own error handler in my project?
Alright, thanks to the comment of RoteS. I finally found what I need to get this done !
The way I did it by Executing the ErrorController wasn't good.
using (Controller controller = new ErrorController())
{
((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
}
I found that by using ServerTransfert instead, we can get ride of customErrors attribute. Here is the final solution (tested):
protected void Application_Error(object sender, EventArgs e)
{
// Response.TrySkipIisCustomErrors = true; I don't know if I will need it someday.
var httpContext = ((HttpApplication)sender).Context;
var exception = Server.GetLastError();
ErrorSignal.FromCurrentContext().Raise(exception);
Server.ClearError();
Response.Clear();
string relativePath = "~/Administration/Error/{0}";
switch (Tools.GetHttpCode(exception))
{
case (int)HttpStatusCode.BadRequest:
Server.TransferRequest(string.Format(relativePath, "BadRequest"));
break;
case (int)HttpStatusCode.Unauthorized:
Server.TransferRequest(string.Format(relativePath, "Unauthorized"));
break;
case (int)HttpStatusCode.Forbidden:
Server.TransferRequest(string.Format(relativePath, "Forbidden"));
break;
case (int)HttpStatusCode.NotFound:
Server.TransferRequest(string.Format(relativePath, "NotFound"));
break;
case (int)HttpStatusCode.InternalServerError:
Server.TransferRequest(string.Format(relativePath, "ServerError"));
break;
default:
Server.TransferRequest(string.Format(relativePath, "DefaultError"));
break;
}
}
Thanks to RoteS for the comment that pointed me in the right direction.
David
I've made some modifications to Global.asax so that I can show custom error pages (403, 404, and 500) Here's the code:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
//FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected void Application_Error(object sender, EventArgs e)
{
if (Context.IsCustomErrorEnabled)
{
ShowCustomErrorPage(Server.GetLastError());
}
}
private void ShowCustomErrorPage(Exception exception)
{
HttpException httpException = exception as HttpException;
if (httpException == null)
{
httpException = new HttpException(500, "Internal Server Error", exception);
}
Response.Clear();
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("fromAppErrorEvent", true);
switch (httpException.GetHttpCode())
{
case 403:
routeData.Values.Add("action", "AccessDenied");
break;
case 404:
routeData.Values.Add("action", "NotFound");
break;
case 500:
routeData.Values.Add("action", "ServerError");
break;
default:
routeData.Values.Add("action", "DefaultError");
routeData.Values.Add("httpStatusCode", httpException.GetHttpCode());
break;
}
Server.ClearError();
IController controller = new ErrorController();
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
}
I've also added the following to my Web.Config:
<customErrors mode="On">
<!-- There is custom handling of errors in Global.asax -->
</customErrors>
The custom error pages show up correctly, and ELMAH will correctly log the error that was (purposefully) thrown. But ELMAH also catches and logs an additional error:
System.InvalidOperationException: The view 'Error' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Views/account/Error.aspx ~/Views/account/Error.ascx ~/Views/Shared/Error.aspx ~/Views/Shared/Error.ascx ~/Views/account/Error.cshtml ~/Views/account/Error.vbhtml ~/Views/Shared/Error.cshtml ~/Views/Shared/Error.vbhtml
My first instincts led me to disabling the global HandleErrorAttribute in the filter configuration. And, similar SO questions such as:MVC problem with custom error pages led me to believe my suspicions were right. But even after disabling the global HandleErrorAttribute I am still getting the Error that the Error view could not be found! What gives? My only other hunch is that my base controller derives from System.Web.Mvc.Controller I tried to examine the source to see if the HandleErrorAttribute is applied to System.Web.Mvc.Controller but couldn't glean anything...
UPDATE:
I tried overriding my base controller to mark exceptions as handled like this:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
base.OnException(filterContext);
}
but that didn't solve the problem.
UPDATE2:
I placed an Error.aspx file into the shared views, just to see what would happen. When it's there, ELMAH logs the forced exception, and then the shared view is served up - it never reaches Application_Error() .... not too sure what to make of it.
Finally got it working to my satisfaction...
The Elmah.Mvc package applies a "hidden" error handler. I've disabled this by adding the following line in web.config <appSettings> (the value was set to "false" by default from nuget install)
<add key="elmah.mvc.disableHandleErrorFilter" value="true" />
So, now my errors propagate up to Application_Error and are logged by Elmah, bypassing the Elmah filter, and display the proper error page (not the one in /shared/error.cshtml)
If you are running in IIS 7 integrated mode, you will need to add Response.TrySkipIisCustomErrors = true; in Application_Error. Otherwise IIS will still redirect the client to a custom error page, despite anything you do in code.
See here for additional details: http://www.west-wind.com/weblog/posts/2009/Apr/29/IIS-7-Error-Pages-taking-over-500-Errors
Edit: here's the body of my Application_Error:
if (HttpContext.Current != null)
{
Server.ClearError();
Response.TrySkipIisCustomErrors = true;
RouteData data = new RouteData();
data.Values.Add("controller", "Error");
data.Values.Add("action", "Error");
IController controller = new MyApp.Controllers.ErrorController();
controller.Execute(new RequestContext(new HttpContextWrapper(Context), data));
}
There are many articles on SO and the web in attempts to handle 404's and exceptions gracefully.
From what I have read the best advice seems to be have a route for 404's like so:
routes.MapRoute(
"404-PageNotFound",
"{*url}",
new { controller = "ErrorController", action = "PageNotFound" }
);
Then for other errors have the HandleError atribute on the Controller and have CustomErrors turned on in web.config so it goes to the error.cshtml page.
However I have read that if you get a exception that does not set the HTTP code to 500 the HandleError will not work.
Can we finally produce an answer/best practice that handles 404's/Exceptions/ASP.Net errors where we can apply to this to all our projects?
Thanks
I use a simple error handling setup. Nice and simple. More info can be found at http://erictopia.com/2010/04/building-a-mvc2-template-part-7-custom-web-errors-and-adding-support-for-elmah/
Install ELMAH and have it handle all the errors.
Next create an Error controller. Add a catch all route like this:
routes.MapRoute(
"ErrorHandler", // Route name
"{*path}", // URL
new { controller = "Error", action = "Index" }
);
Then in web.config add this section:
<customErrors mode="RemoteOnly" defaultRedirect="/Error/Index">
<error statusCode="403" redirect="/Error/NoAccess" />
<error statusCode="404" redirect="/Error/NotFound" />
</customErrors>
No need to set up a 404 route.
In global asax application start, set up a global filter to catch 404 where the controller exists but not the action, or if an action returns a 404 result.
filters.Add(new HttpNotFoundFilterAttribute { Order = 99 });
where the filter is an ActionFilterAttribute with this override:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result !=null &&
(filterContext.Result.GetType() == typeof(HttpNotFoundResult) )
{
//You can transfer to a known route for example
filterContext.Result = new TransferResult(SomeAction, SomeController);
}
}
And also in Application_Error, in case no controller exists:
Exception ex = Server.GetLastError();
string uri = null;
if (Context != null && Context.Request != null)
{
uri = Context.Request.Url.AbsoluteUri;
}
Exception baseEx = ex.GetBaseException();
var httpEx = ex as HttpException;
if ((httpEx != null && httpEx.GetHttpCode()==404)
|| (uri != null && Context.Response.StatusCode == 404) )
{ /* do what you want. */
//Example: show some known url
Server.ClearError();
Server.TransferRequest(transferUrl);
}
To avoid handling 404 for static resources, you should install SP1 on Windows 7 or windows 2008 R2 to upgrade IIS7 and set in web.config:
...
<modules runAllManagedModulesForAllRequests="false">
...
In my ASP.NET web application, I have defined custom error pages in my web.config file as follows:
<customErrors mode="On" defaultRedirect="~/default.html">
<error statusCode="404" redirect="~/PageNotFound.html" />
</customErrors>
In the case of a 404 error, my site redirects to the default.html page, but it passes "aspxerrorpath" as a query string parameter to the custom error page as follows:
http://www.example.com/default.html?aspxerrorpath=/somepathcausederror/badpage.aspx
I don't want that behavior. I want the redirect URL to simply read:
http://www.example.com/default.html
Is there a way to achieve this?
If you supply your own query string variable when specifying the path, then .NET will NOT tack on the "aspxerrorpath". Who knew?
For example:
<customErrors mode="On" defaultRedirect="errorpage.aspx?error=1" >
This will do the trick.
I had to add this to a bunch of apps since URLScan for IIS by default rejects anything with "aspxerrorpath" in it anyway.
In the global.asax, catch the 404 error and redirect to the file not found page. I didn't require the aspxerrorpath and it worked a treat for me.
void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
if (ex is HttpException && ((HttpException)ex).GetHttpCode() == 404)
{
Response.Redirect("~/filenotfound.aspx");
}
else
{
// your global error handling here!
}
}
You could just send your own url params to the error page
<customErrors mode="On" defaultRedirect="~/default.html?404">
<error statusCode="404" redirect="~/PageNotFound.html?404" />
</customErrors>
My first thought would be to create a HttpHandler which catches url's with aspxerrorpath in it, and strips it. You could probably do the same with the rewrite module in IIS7 as well.
I think you'd instead implement/use the Application_Error event in Global.asax, and do your processing/redirects there.
Providing you call Server.ClearError in that handler, I don't think it will use the customErrors config at all.
I use javascript like
if (location.search != "") { window.location.href = "/404.html"; }
If you remove aspxerrorpath=/ and you use response redirect during error handling you'll get exception there will be redirection loop.
Add redirectMode="ResponseRewrite" in the Custom Error like this,
<customErrors mode="On" defaultRedirect="~/NotFound">
<error statusCode="404" redirect="~/NotFound" redirectMode="ResponseRewrite"/>
</customErrors>
this solution works for me.
The best solution (more a workaround..) I implemented since now to prevent aspxerrorpath issue continuing to use ASP.NET CustomErrors support, is redirect to the action that implements Error handling.
These are some step of my solution in an ASP.NET MVC web app context:
First enable custom errors module in web.config
<customErrors mode="On" defaultRedirect="~/error/500">
<error statusCode="404" redirect="~/error/404"/>
</customErrors>
Then define a routing rule:
routes.MapRoute(
name: "Error",
url: "error/{errorType}/{aspxerrorpath}",
defaults: new { controller = "Home", action = "Error", errorType = 500, aspxerrorpath = UrlParameter.Optional },
);
Finally implement following action (and related views..):
public ActionResult Error(int errorType, string aspxerrorpath)
{
if (!string.IsNullOrEmpty(aspxerrorpath)) {
return RedirectToRoute("Error", errorType);
}
switch (errorType) {
case 404:
return View("~/Views/Shared/Errors/404.cshtml");
case 500:
default:
return View("~/Views/Shared/Errors/500.cshtml");
}
}
In my case, i prefer not use Web.config. Then i created code above in Global.asax file:
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
//Not Found (When user digit unexisting url)
if(ex is HttpException && ((HttpException)ex).GetHttpCode() == 404)
{
HttpContextWrapper contextWrapper = new HttpContextWrapper(this.Context);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "NotFound");
IController controller = new ErrorController();
RequestContext requestContext = new RequestContext(contextWrapper, routeData);
controller.Execute(requestContext);
Response.End();
}
else //Unhandled Errors from aplication
{
ErrorLogService.LogError(ex);
HttpContextWrapper contextWrapper = new HttpContextWrapper(this.Context);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
IController controller = new ErrorController();
RequestContext requestContext = new RequestContext(contextWrapper, routeData);
controller.Execute(requestContext);
Response.End();
}
}
And thtat is my ErrorController.cs
public class ErrorController : Controller
{
// GET: Error
public ViewResult Index()
{
Response.StatusCode = 500;
Exception ex = Server.GetLastError();
return View("~/Views/Shared/SAAS/Error.cshtml", ex);
}
public ViewResult NotFound()
{
Response.StatusCode = 404;
return View("~/Views/Shared/SAAS/NotFound.cshtml");
}
}
And that is my ErrorLogService.cs
//common service to be used for logging errors
public static class ErrorLogService
{
public static void LogError(Exception ex)
{
//Do what you want here, save log in database, send email to police station
}
}
If you want to resolve or handle error request you can insert into Handler try catch statement.
like this:
try {
// Block of code that generate error
}
catch(Exception e) {
// Block of code to handle errors ||| HERE you can put error in your response and handle it without get xhr redirect error.
}