I've struggled a couple of days with the error handling in MVC. I still do not have got it right. (I've also read most of the questions here at SO and googled until my fingers bled)
What I want to do:
Use the standard [Authorize] attribute
Redirect all errors to my error controller (including unauthorized)
Have one action per HTTP error in my error controller.
What I do not want to do:
Put the [ErrorHandler] on all of my controllers (can it be used on my base controller)?
Use a custom Authorize attribute.
Actually I could do anything necessary (including the NOT list) as long as I get #1-3 working.
What I've tried:
Using Application_Error
Using Controller.HandleUnknownAction
Using Controller.OnException
Using [ErrorHandler] on my controllers
Turning on/off CustomErrors in web.config
Guess I need a combination of those or maybe something else?
You could also handle all your error logic in the Application_Error of your Global.asax.cs and then route them dynamically bases on the different HTTP status codes:
protected void Application_Error(object sender, EventArgs e)
{
var ex = Server.GetLastError().GetBaseException();
var routeData = new RouteData();
if (ex.GetType() == typeof(HttpException))
{
var httpException = (HttpException)ex;
switch (httpException.GetHttpCode())
{
case 401:
routeData.Values.Add("action", "NotAuthorized");
break;
case 403:
routeData.Values.Add("action", "NotAuthorized");
break;
case 404:
routeData.Values.Add("action", "PageNotFound");
break;
default:
routeData.Values.Add("action", "GeneralError");
break;
}
}
else
{
routeData.Values.Add("action", "GeneralError");
}
routeData.Values.Add("controller", "Error");
routeData.Values.Add("error", ex);
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
It seems the 401 doesn't throw necessary a HttpException so you want to handle that manually to fit the logic:
protected void Application_EndRequest(object sender, EventArgs e)
{
if (Context.Response.StatusCode == 401)
{
throw new HttpException(401, "You are not authorised");
}
}
And yes, your controllers inherit the attributes from your base conroller.
You can use a custom filter, just extend ErrorHandlerAttribute and make it aware of your error controller. Than add it as a global filter inside Application_Start:
GlobalFilters.Filters.Add(new CustomAErrorHandlerAttribute());
The proper way is not to use Application_Error. The proper way is to use [HandleError] attribute in combination with the customErrors section in web.config as I describe here:
http://blog.gauffin.org/2011/11/how-to-handle-errors-in-asp-net-mvc/
Related
I'm using MVC5 with the routing attribute decoration on my controller actions. I would like to use the Controller.Execute command inside the Application_Error function to transfer the call to mvc but i'm getting the exception "A public action method 'HttpError404' was not found on controller 'ErrorsController'.".
If i remove the RouteAttribute decoration, everything is working but i do need the RouteAttribute decoration. Why is it happening and how can i solve this problem considering that i don't want to use the HttpContext.Current.Server.TransferRequest trick?
ErrorsController
[RoutePrefix("Errors")]
public class ErrorsController : Controller
{
[Route("HttpError404")]
public ActionResult HttpError404()
{
return View();
}
}
Application_Error
protected void Application_Error(object sender, EventArgs e)
{
var routeData = new RouteData();
routeData.Values.Add("controller", "errors");
routeData.Values.Add("action", "HttpError404");
var requestContext = new RequestContext(new HttpContextWrapper(HttpContext.Current), routeData);
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controller = controllerFactory.CreateController(requestContext, "errors");
try
{
controller.Execute(requestContext);
}
catch (Exception ex)
{
//A public action method 'HttpError404' was not found on controller 'ErrorsController'.
}
}
I reproduced your issue and got this error output log:
System.Web.HttpException was caught
HResult=-2147467259 (0x80004005)
Message=A public action method 'HttpError404' was not found on controller
'[ApplicationName].Controllers.ErrorsController'.
Source=System.Web.Mvc
ErrorCode=-2147467259
WebEventCode=0
StackTrace:
at System.Web.Mvc.Controller.HandleUnknownAction(String actionName)
at System.Web.Mvc.Controller.ExecuteCore()
at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext
requestContext)
at [ApplicationName].MvcApplication.Application_Error(Object sender, EventArgs e) in
[ProjectPath]\[ApplicationName]\[ApplicationName]\Global.asax.cs:line XX
By seeing exception details above, the exception originated from System.Web.Mvc.Controller.HandleUnknownAction method which by default looks like this (taken from aspnetwebstack reference):
protected virtual void HandleUnknownAction(string actionName)
{
// If this is a direct route we might not yet have an action name
if (String.IsNullOrEmpty(actionName))
{
throw new HttpException(404, String.Format(CultureInfo.CurrentCulture, MvcResources.Controller_UnknownAction_NoActionName, GetType().FullName));
}
else
{
throw new HttpException(404, String.Format(CultureInfo.CurrentCulture, MvcResources.Controller_UnknownAction, actionName, GetType().FullName));
}
}
As a workaround, try overriding that method by returning view from corresponding action method (using same name as action method name) as given in example below:
public class ErrorsController : Controller
{
// put error action methods here
protected override void HandleUnknownAction(string actionName)
{
// returns view from given action name
// better to use try-catch block just in case the view name is not exist
this.View(actionName).ExecuteResult(this.ControllerContext);
}
}
Note 1: Use if-condition or switch...case block if you have different view names against action method names.
Note 2: It is possible that standard routing messed up while RouteAttribute is enabled and calling HandleUnknownAction by unknown reason, I recommend to remove attribute routing if corresponding action method called with IController.Execute.
References:
MVC 5 Attribute Routing not Working with Controller Execute
Handling Unknown Actions in ASP.NET MVC
How to show custom error page ,when Http error occured without changing url.
When Http error occured then how to show customer custom error page without routing to another Url.
The below method will not use a redirect - it will return your custom error + correct httpstatus code as an immediate response to the client, by catching the error in the application_error method and then choosing what to return within the same response, removing the need to redirect.
Create an ErrorController - this allows you to tailor your end-user error pages and status codes.:
[AllowAnonymous]
public class ErrorController : Controller
{
public ActionResult PageNotFound()
{
Response.StatusCode = 404;
return View();
}
public ActionResult ServerError()
{
Response.StatusCode = 500;
return View();
}
public ActionResult UnauthorisedRequest()
{
Response.StatusCode = 403;
return View();
}
//Any other errors you want to specifically handle here.
public ActionResult CatchAllUrls()
{
//throwing an exception here pushes the error through the Application_Error method for centralised handling/logging
throw new HttpException(404, "The requested url " + Request.Url.ToString() + " was not found");
}
}
Add a route to catch all urls to the end of your route config - this captures all 404's that are not already caught by matching existing routes:
routes.MapRoute("CatchAllUrls", "{*url}", new { controller = "Error", action = "CatchAllUrls" });
In your global.asax:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
//Error logging omitted
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
IController errorController = new Controllers.ErrorController();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("area", "");
routeData.Values.Add("ex", exception);
if (httpException != null)
{
//this is a basic exampe of how you can choose to handle your errors based on http status codes.
switch (httpException.GetHttpCode())
{
case 404:
Response.Clear();
// page not found
routeData.Values.Add("action", "PageNotFound");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
case 500:
// server error
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
case 403:
// server error
routeData.Values.Add("action", "UnauthorisedRequest");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
//add cases for other http errors you want to handle, otherwise HTTP500 will be returned as the default.
default:
// server error
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
}
}
//All other exceptions should result in a 500 error as they are issues with unhandled exceptions in the code
else
{
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
}
Add following <customErrors> tag in the web.config that helps you to redirect to the NotFound action method of Error controller if the system fails to find the requested url (status code 404) and redirects to ServerError action method of error controller if the system fires an internal server error (status code 500)
<!--<Redirect to error page>-->
<customErrors mode="On" defaultRedirect="~/Error/ServerError">
<error redirect="~/Error/NotFound" statusCode="404" />
</customErrors>
<!--</Redirect to error page>-->
You have to create an Error controller that contains ServerError and NotFound action method that renders the related view to display the proper message to the user.
public class ErrorController : Controller
{
public ActionResult NotFound()
{
return View();
}
public ActionResult Error()
{
return View();
}
}
Edit 2:
For remain the same url while custom error occurs, you need to install Magical Unicorn Mvc Error Toolkit 2.2.2 nuget package.
you can follow the following steps to work with:
Open solution in visual studio
Go to Tools menu and select Nuget Package Manager
Select Package Manager Console from sub menu
Write PM> Install-Package MagicalUnicorn.MvcErrorToolkit in Package Manager Console
you can visit here for more info about the nuget package
It will install the nuget package that will work as per your requirement.
I've spent the day going through a dozen or more sites, blogs, and SO answers, all claiming to provide the "right" way to implement custom error handling, in ASP.NET MVC.
None of them agree with each other, and most of them don't seem to work.
What I want:
Custom pages for the standard HTTP errors
That have the same look-and-feel as the standard pages of my site
Without having to completely redo everything I have in my Shared/_Layout.cspx
That work whether the user is authenticated or not
But that don't provide access to anything the user should be required to be authenticated to access
That should properly return the URL requested with the appropriate HTTP error code, rather than redirecting to a different page that returns HTTP 200
That redirects anything that isn't a valid route to the 404 page, whether it is a missing action, a missing controller, or a missing route
That allows for handling any sub-codes, like 404.3, that we need handled separately
That works the same in IIS7, IIS Express, and in Casini
That works in MVC5, but will continue to work in MVC6, MVC7, and whatever.
That doesn't involve separate code being added to each and every page
And, I want this to be the canonical answer. Not just "this is what I do", but "this is what Microsoft's developers intended and expected to be done".
Any ideas?
I'm not sure if this is the best way but it works for me and its seo friendly:
web.config
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound" />
</httpErrors>
</system.webServer>
you can define sub status codes too:
<error statusCode="404" subStatusCode="2" responseMode="ExecuteURL" path="/Error/NotFound" />
and this is how my error controller looks like:
public class ErrorController : Controller
{
//
// GET: /Error/
public ActionResult NotFound()
{
// you have to set your error codes manually
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
}
The following solution meets most of your requirements except:
But that don't provide access to anything the user should be required to be authenticated to access This is very dependent on your architecture of your application, however this method does not add any risk of this happening
That works the same in IIS7, IIS Express, and in Casini not used Casini so can't guarantee this
That works in MVC5, but will continue to work in MVC6, MVC7, and whatever. Haven't tried this in MVC6
That allows for handling any sub-codes, like 404.3, that we need handled separately It depends what you mean by this - you can return a Response.SubStatusCode to the client in your action results. If you mean to handle it within the application, then provided it is in the exception object thrown, you can access it.
Create an ErrorController - this allows you to tailor your end-user error pages and status codes.:
[AllowAnonymous]
public class ErrorController : Controller
{
public ActionResult PageNotFound()
{
Response.StatusCode = 404;
return View();
}
public ActionResult ServerError()
{
Response.StatusCode = 500;
return View();
}
public ActionResult UnauthorisedRequest()
{
Response.StatusCode = 403;
return View();
}
//Any other errors you want to specifically handle here.
public ActionResult CatchAllUrls()
{
//throwing an exception here pushes the error through the Application_Error method for centralised handling/logging
throw new HttpException(404, "The requested url " + Request.Url.ToString() + " was not found");
}
}
Add a route to catch all urls to the end of your route config - this captures all 404's that are not already caught by matching existing routes:
routes.MapRoute("CatchAllUrls", "{*url}", new { controller = "Error", action = "CatchAllUrls" });
In your global.asax:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
//Error logging omitted
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
IController errorController = new Controllers.ErrorController();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("area", "");
routeData.Values.Add("ex", exception);
if (httpException != null)
{
//this is a basic exampe of how you can choose to handle your errors based on http status codes.
switch (httpException.GetHttpCode())
{
case 404:
Response.Clear();
// page not found
routeData.Values.Add("action", "PageNotFound");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
case 500:
// server error
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
case 403:
// server error
routeData.Values.Add("action", "UnauthorisedRequest");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
//add cases for other http errors you want to handle, otherwise HTTP500 will be returned as the default.
default:
// server error
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
break;
}
}
//All other exceptions should result in a 500 error as they are issues with unhandled exceptions in the code
else
{
routeData.Values.Add("action", "ServerError");
Server.ClearError();
// Call the controller with the route
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
}
In Summary:
Custom pages for the standard HTTP errors You have full control of the page returned to the user
That have the same look-and-feel as the standard pages of my site You can use your existign layout if you want to for the custom pages
Without having to completely redo everything I have in my Shared/_Layout.cspx as above
That work whether the user is authenticated or not This is partly down to your architecture but there is nothing in the proposed method that will interfere with user authentication
That should properly return the URL requested with the appropriate HTTP error code, rather than -redirecting to a different page that returns HTTP 200 There is no redirect to the error page so the correct http status code is retured
That redirects anything that isn't a valid route to the 404 page, whether it is a missing action, a missing controller, or a missing route
That doesn't involve separate code being added to each and every page.
There are 2 types of error handling.
Page-level errors
Application-level erros
For page-level errors you can use both web.config or you can create base controller and override OnException event.
protected override void OnException(ExceptionContext filterContext) { ... }
For application-level errors you can use global.asax Application_Error event
void Application_Error(object sender, EventArgs e) { ... }
Also you can check this link: https://www.simple-talk.com/dotnet/asp.net/handling-errors-effectively-in-asp.net-mvc/
One Way could be:
ErrorFilter:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
HandleErrorAttribute attribute = new HandleErrorAttribute();
filters.Add(attribute);
}
Override OnException method of controller
protected override void OnException(ExceptionContext filterContext)
{
EventLog.WriteEntry("YourProjectEventName", filterContext.Exception.ToString(), EventLogEntryType.Error);
base.OnException(filterContext);
}
Ref link:
http://www.prideparrot.com/blog/archive/2012/5/exception_handling_in_asp_net_mvc
http://www.codeproject.com/Articles/422572/Exception-Handling-in-ASP-NET-MVC
Hope this helps.
First off, I'm not posting this lightly. I've just spent a near-record half a day trying to solve this.
I'm developing an ASP.NET MVC website using Visual Studio 2012 on a laptop. I have implemented error-handling, which works, by adding this code to my GLOBAL.ASAX file using this approach:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Server.ClearError();
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
routeData.Values.Add("exception", exception);
if (exception.GetType() == typeof(HttpException))
{
routeData.Values.Add("statusCode", ((HttpException)exception).GetHttpCode());
}
else
{
routeData.Values.Add("statusCode", 500);
}
IController controller = new ErrorController();
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Response.End();
}
I've got a view which generates an error in the Razor mark-up:
#{
// trigger an error
int i =1/0;
}
When I browse to this view, it triggers an error, taking me to controller Error, action Index, where this bit of code shows an appropriate view:
public ActionResult Index(int statusCode, Exception exception){
Response.StatusCode = statusCode;
return View(); }
So far, so good. My question is - how can I get a 404 to be handled in the same way? So if I go to a non-existent page, I get:
Things I've tried:
Every possible arrangement of CustomErrors - On / Off in web.config
Debugging global.asax to check it's running on application start-up
lots of other things, I'm sure, which I've now forgotten!
The web server being used by Visual Studio is IIS 8 Express, if that's of any help.
There are plenty ways to do that.
Simplest way
Download and install this nuget - NotFoundMvc and give this blog post a read.
Or check out this beautiful answer on SO, orignally taken from Real world error hadnling in ASP.NET MVC RC2. But since the current state of the source site is in horrible design state, i ll repost the code here below.
Goal:
To catch every error that occures on server include HttpExeptions like 404 (Page not found).
The ability to choose a specific View (error Template) for some errors and a generic View/Behaviour if nor specific has been specified.
Scenario 1:
anonymus user typed a wrong url.
Actions:
display a user friendly message like: "OOPS, The page is missing… back to home"
Log the error to .log file on server.
Send email to admin.
Senario 2:
same as scenario 1 but one difference : user is anonymus but his IP is from our company office.
Actions:
show the detailed error.
Let's see some code:
protected void Application_Error(object sender, EventArgs e) {
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;
}
}
// Pass exception details to the target error View.
routeData.Values.Add("error", exception);
// Clear the error on server.
Server.ClearError();
// Call target Controller and pass the routeData.
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
Add this method to Global.asax:
Created a new controller called ErrorController:
You can specify some more behaviors to another eception types, I breated a view just for error: 404, 500.
public ActionResult HttpError404(string error) {
ViewData["Title"] = "Sorry, an error occurred while processing your request. (404)";
ViewData["Description"] = error;
return View("Index");
}
public ActionResult HttpError500(string error) {
ViewData["Title"] = "Sorry, an error occurred while processing your request. (500)";
ViewData["Description"] = error;
return View("Index");
}
public ActionResult General(string error) {
ViewData["Title"] = "Sorry, an error occurred while processing your request.";
ViewData["Description"] = error;
return View("Index");
}
You need to catch this in the routing engine of MVC. Add a route like this:
routes.MapRoute(
name: "NotFound",
url: "{*url}",
defaults: new { controller = "Error", action = "Http404" }
);
Then in your ErrorController add this action:
public ActionResult Http404()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
Yasser's link led me to an answer which seems to work. In the web.config file section, add:
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL" path="/Error/PageNotFound" />
</httpErrors>
You can then create a router and controller for this path, and it all seems to work. Is it really that simple?
How do you stop asp.net mvc from redirecting to an error page when it was a json post? It keeps trying to redirect to the error path in my config file when an exception occurs and I need it to return the exception...also it doesn't even try and direct them to the correct path - it keeps looking for an Error.cshtml view under Home (the Home folder for views, as if it is trying to use my home controleler), when they are all under an Error folder/controller set up, which is clearly outlined in my config file:
<customErrors mode="On" defaultRedirect="~/Error/Error">
<error statusCode="404" redirect="~/Error/NotFound" />
<error statusCode="403" redirect="~/Error/Forbidden" />
</customErrors>
Thanks!
you can do it by registering Application_Error event in Global.asax and then redirect it to an error controller with data in this way or you can perform what ever function you want depending upon error code
protected void Application_Error()
{
var exception = Server.GetLastError();
var httpException = exception as HttpException;
Response.Clear();
Server.ClearError();
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "oops";
routeData.Values["exception"] = exception;
Response.StatusCode = 500;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (Response.StatusCode)
{
case 403:
routeData.Values["action"] = "NoAccess";
break;
case 404:
routeData.Values["action"] = "NotFound";
break;
}
}
IController errorsController = new ErrorController();
var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
errorsController.Execute(rc);
}
then make a controller with actions oops / NotAccess / NotFound like
public ActionResult oops(Exception exception)
{
//return Content("General failure", "text/plain");
// return View();
}
public ActionResult NotFound()
{
// return Content("Not found", "text/plain");
// return View("oops");
}
public ActionResult NoAccess()
{
//return Content("Forbidden", "text/plain");
//return View("oops");
}
And Return your own view with proper information
Solution 1:
You can have BASE controller for all your controllers (it is good idea to have it) and in this controller you can override OnException method like this:
protected override void OnException(ExceptionContext filterContext)
{
if (Request.IsAjaxRequest())
{
// TODO
}
else
{
base.OnException(filterContext);
}
}
Solution 2:
You can create Custom Action Filter to do the job and use method above. Than you can register this filter in global.asax or decorate only those controllers/actions you need.
general idea is to use:
Request.IsAjaxRequest()
Hope it helps. Good luck and have fun :)