How can I correctly handle exceptions thrown from controllers in ASP.NET MVC? The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
Using this web.config
<customErrors mode="On">
<error statusCode="401" redirect="/Errors/Http401" />
</customErrors>
with the following code
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
// Force a 401 exception for testing
throw new HttpException(401, "Unauthorized");
}
}
}
doesn't result in what I was hoping for. Instead I get the generic ASP.NET error page telling me to modify my web.config to see the actual error information. However, if instead of throwing an exception I return an invalid View, I get the /Shared/Views/Error.aspx page:
return View("DoesNotExist");
Throwing exceptions within a controller like I've done above seems to bypass all of the HandleError functionality, so what's the right way to create error pages and how do I play nice with the MVC infrastructure?
Controller.OnException(ExceptionContext context). Override it.
protected override void OnException(ExceptionContext filterContext)
{
// Bail if we can't do anything; app will crash.
if (filterContext == null)
return;
// since we're handling this, log to elmah
var ex = filterContext.Exception ?? new Exception("No further information exists.");
LogException(ex);
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = !(filterContext.Exception == null),
ShowLink = false
};
filterContext.Result = View("ErrorPage", data);
}
Thanks to kazimanzurrashaid, here is what I wound up doing in Global.asax.cs:
protected void Application_Error()
{
Exception unhandledException = Server.GetLastError();
HttpException httpException = unhandledException as HttpException;
if (httpException == null)
{
Exception innerException = unhandledException.InnerException;
httpException = innerException as HttpException;
}
if (httpException != null)
{
int httpCode = httpException.GetHttpCode();
switch (httpCode)
{
case (int) HttpStatusCode.Unauthorized:
Response.Redirect("/Http/Error401");
break;
}
}
}
I'll be able to add more pages to the HttpContoller based on any additional HTTP error codes I need to support.
The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
That is just wrong. Indeed, HandleError will only "process" exceptions either thrown in your own code or in code called by your own code. In other words, only exceptions where your action is in the call stack.
The real explanation for the behavior you're seeing is the specific exception you're throwing. HandleError behaves differently with an HttpException. From the source code:
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500) {
return;
}
I don't think you will be able to show specific ErrorPage based upon the HttpCode with the HandleError Attribute and I would prefer to use an HttpModule for this purpose. Assuming that I have folder "ErrorPages" where different page exists for each specific error and the mapping is specifed in the web.config same as the regular web form application. And the following is the code which is used to show the error page:
public class ErrorHandler : BaseHttpModule{
public override void OnError(HttpContextBase context)
{
Exception e = context.Server.GetLastError().GetBaseException();
HttpException httpException = e as HttpException;
int statusCode = (int) HttpStatusCode.InternalServerError;
// Skip Page Not Found and Service not unavailable from logging
if (httpException != null)
{
statusCode = httpException.GetHttpCode();
if ((statusCode != (int) HttpStatusCode.NotFound) && (statusCode != (int) HttpStatusCode.ServiceUnavailable))
{
Log.Exception(e);
}
}
string redirectUrl = null;
if (context.IsCustomErrorEnabled)
{
CustomErrorsSection section = IoC.Resolve<IConfigurationManager>().GetSection<CustomErrorsSection>("system.web/customErrors");
if (section != null)
{
redirectUrl = section.DefaultRedirect;
if (httpException != null)
{
if (section.Errors.Count > 0)
{
CustomError item = section.Errors[statusCode.ToString(Constants.CurrentCulture)];
if (item != null)
{
redirectUrl = item.Redirect;
}
}
}
}
}
context.Response.Clear();
context.Response.StatusCode = statusCode;
context.Response.TrySkipIisCustomErrors = true;
context.ClearError();
if (!string.IsNullOrEmpty(redirectUrl))
{
context.Server.Transfer(redirectUrl);
}
}
}
One other possibility (not true in your case) that others reading this may be experiencing is that your error page is throwing an error itself, or is not implementing :
System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>
If this is the case then you will get the default error page (otherwise you'd get an infinite loop because it would keep trying to send itself to your custom error page). This wasn't immediately obvious to me.
This model is the model sent to the error page. If your error page uses the same master page as the rest of your site and requires any other model information then you will need to either create your own [HandleError] type of attribute or override OnException or something.
protected override void OnException (ExceptionContext filterContext )
{
if (filterContext != null && filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
this.View("Error").ViewData["Exception"] = filterContext.Exception.Message;
this.View("Error").ExecuteResult(this.ControllerContext);
}
}
I chose the Controller.OnException() approach, which to me is the logical choice - since I've chosen ASP.NET MVC, I prefer to stay at the framework-level, and avoid messing with the underlying mechanics, if possible.
I ran into the following problem:
If the exception occurs within the view, the partial output from that view will appear on screen, together with the error-message.
I fixed this by clearing the response, before setting filterContext.Result - like this:
filterContext.HttpContext.Response.Clear(); // gets rid of any garbage
filterContext.Result = View("ErrorPage", data);
Jeff Atwood's User Friendly Exception Handling module works great for MVC. You can configure it entirely in your web.config, with no MVC project source code changes at all. However, it needs a small modification to return the original HTTP status rather than a 200 status. See this related forum post.
Basically, in Handler.vb, you can add something like:
' In the header...
Private _exHttpEx As HttpException = Nothing
' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
_exHttpEx = CType(ex, HttpException)
HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If
Related
For example, we have simple action method to show any book with proper id:
public ActionResult GetBook(int id) // id = 123456789
{
var book = dataManager.Books.GetBookById(id); // == null
logger.Info("Getting book with id " + book.Id);
return View(book);
}
If id parameter is not valid we get 500 error because there is no book with that id.
We have to handle this situation manually if we need to throw 404 error, like this:
if (book == null)
{
return HttpNotFound();
}
Is it possible to switch 500-error to 404-error for all action methods somewhere in web-application (custom filter, request pipeline)?
Technically you could, by installing an error handler, rewrite any 500 Internal Server Error to a 404 Not Found in Application_Error() as explained in ASP.NET MVC 5 error handling.
However, you seem to want to let your code throw a NullReferenceException on a null book in book.Id, and turn that exception into a 404.
You really shouldn't be doing that in this case, because this will hide programming errors, because you will miss the exceptions where this happened unintentionally.
So: just do this explicitly where you do expect a null. The code you showed is exactly what you need:
if (book == null)
{
return HttpNotFound();
}
I found this helpful Bulk 301 Redirect
I test then throw a 302
try
{
------
}
catch
{
throw new HttpException(302, "not found");
}
Catch the exception in the Global.asax and look up a redirect csv file
protected void Application_Error()
{
Exception exception = Server.GetLastError();
Response.Clear();
Server.ClearError();
HttpException ex = exception as HttpException;
if (ex.GetHttpCode() == 404 || ex.GetHttpCode() == 302 || ex.GetHttpCode() == 500)
{
Redirect code in the link!
}
}
I have CustomHandleErrorAttribute where i override OnException method like this:
public override void OnException(ExceptionContext filterContext) {
if (!filterContext.ExceptionHandled) {
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest") {
filterContext.Result = new JsonResult {
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new {
error = true,
message = filterContext.Exception.Message
}
};
}
else {
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult {
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
}
}
}
I can get exceptions by AJAX request and while my Action is processing.
I have a problem, when exception thrown (for example on page /Customers/Customer/1), i have yellow screen with Server Error in '/' Application, but i would like to display my view and pass to ViewData information about exception handled, and then display in on this page (do not redirect to CustomErrorPage or anywhere else).
So:
1. If i have exception- just display exception info without form;
2. If i don't have exception- display form;
Is it possible, or thrown exception could not continue processing action and displaying same view?
Thx.
You get the yellow screen of death when you allow an exception to bubble all the way up to the level of IIS without being caught. This becomes a 500 Server Error and you will get the yellow screen or your 500 error page, if you've set one. There's nothing else that can be displayed because there's no way to recover from the error.
The only way to get the same view to load is to catch every exception and respond to it in some way. That might be generating an error message for the user on the view or something else, but it's up to you to catch the exception and respond, IIS will not do this for you.
I am working on a website which is API based, client side is being developed in .Net MVC. For exception handling, I am using
public void Application_Error(object sender, EventArgs e)
{
string action = "Index";
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
if (httpException != null)
{
switch (httpException.GetHttpCode())
{
case 404:
// page not found
action = "Error404";
break;
default:
action = "Index";
break;
}
// clear error on server
Server.ClearError();
}
Response.Redirect(String.Format("/error/{0}", action));
}
so for any exception thrown by try catch from Controller, the page redirects to error page.
Now I want that when session is expired it should redirect to Login page, How can I do that?
Right now what is happening is, after session expires, when I try to access the session value, it throws exception "object reference not set to an instance of object." then it redirects to the default error page.
I don't think you're going to be able to do this from inside a generic exception handler because - as you said - missing session variables simply throw a NullReferenceException. Perform a null check on the session variable from your controller:
Public ActionResult MyAction ()
{
if (Session["myVariable"] == null)
{
RedirectToAction("SessionTimeOut", "Error");
}
...
}
If you have session variables that should always exist unless the session has expired, you could try overriding the OnActionExecuting method for your controller and performing your null check in there. To do this for multiple controllers, define a BaseController, override its OnActionExecuting method and then inherit this in your other controllers.
I use request validation on asp.net mvc 2 on .NET 4, and whenever I post htmlcode to my controller action. that is good.
But I see now my yellow screen of death.
Instead of that I want to redirect the user to my custom error page for this.
What do I need to change in my web.config to redirect to
~/Home/InvalidInput
for example.
You want to add that info to the customErrors element in the web.config. This element is defined under the system.web tag.
<customErrors mode="RemoteOnly" defaultRedirect="/Home/InvalidInput " />
Also, you will need to define a route to handle this redirect since that is nothing more than a URL. Just specify an route and an action in your HomeController that will return the InvalidInput view when this route is hit.
Setup a custom base controller that HomeController (and whatever others you have) inherits from. Then you can setup exception handling in the base controller:
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
base.OnException(filterContext);
return;
}
filterContext.ExceptionHandled = true;
if (filterContext.Exception is HttpException)
{
var statusCode = ((HttpException) filterContext.Exception).GetHttpCode();
Response.StatusCode = statusCode;
if (statusCode == (int) HttpStatusCode.NotFound)
{
filterContext.Result = View(ErrorController.Actions.NotFound);
}
else
{
filterContext.Result = View(ErrorController.Actions.InternalServerError);
}
}
else
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
filterContext.Result = View(ErrorController.Actions.InternalServerError);
}
base.OnException(filterContext);
}
Of course you will need your own ErrorController and error views as well.
The benefit in this is that a) you have greater control over how exceptions are handled, b) you can set the appropriate HTTP status code, and c) you can unit test your error handler.
The defaultRedirect property of customErrors will work as well as a more basic approach, however you cannot unit test the behavior, and users are then exposed to your error routes rather than staying on the current URI.
So if I understand [HandleError] correctly (see here) you have to add it to each Controller that you want to have errors handled on.
It seems much easier to just add the path of your error page into the web.config customErrors tag:
<customErrors mode="On" defaultRedirect="~/Error/Index" >
</customErrors>
In what situations would using [HandleError] be better than that?
In [HandleError] you can achieve quite a lot. You can log the error. You can also figure out the kind of error and based on situation you can redirect the user to certain page.Following is one sample -
public class HandleErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled)
return;
string referrerController = string.Empty;
string referrerAction = string.Empty;
if (filterContext.HttpContext.Request.UrlReferrer != null)
{
string[] segments = filterContext.HttpContext.Request.UrlReferrer.Segments;
if (segments.Length > 1)
{
referrerController = segments[1] != null ? segments[1].Replace("/", string.Empty) : string.Empty;
}
if (segments.Length > 2)
{
referrerAction = segments[2] != null ? segments[2].Replace("/", string.Empty) : string.Empty;
}
}
filterContext.Controller.TempData["exception"] = filterContext.Exception.Message;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(
new { controller = referrerController , action = referrerAction}));
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
}
}
In this code I am saving exception message to TempData, so that I can show the error message to user. This is just one example but you can do anything that your requirements demand. Here I am creating my own [HandleError] attribute by inheriting from FilterAttribute and implementing IExceptionFilter. You can see the kind of power I am getting here. I implemented my own attribute to handle my requirements. But you can achieve the similar results by using built in [HandleError].
Purpose of line no. 2 is to handle a scenario where somebody else in chain has already handled the exception. Then in that case you might not be interested to handle it again. Response.Clear() is to clear the pipe before I redirect user to new page. It is not necessary to be there in your case.
Any attribute can applied to all controllers globally in FilterConfig.RegisterGlobalFilters:
filters.Add(new HandleErrorAttribute());
Can be done for API controllers too in the relevant method, i.e. WebApiConfig.Register.
However, if you only need to display a simple error page just use customErrors.