Ensure that a specific exception always results in a given HTTP response status code
Ensure that a specific exception always results in a given HTTP response status code
Requirement
I have an ASP.Net MVC application that works with a number of different libraries. As with most libraries, various function calls may cause one of many different exceptions to be thrown.
Currently, whenever any exception is thrown, then MVC application 'handles' them and returns an "internal server error" (code 500) back to the client (eg. web browser).
This is fine for most cases, however, there is one specific exception type (in this case UnauthorizedAccessException
) that I would like to result in an "Unauthorized" (code 401) status being sent in the response, instead of the usual 500 error.
UnauthorizedAccessException
Current Attempt
I did a fair bit of research and it looks like the bets way to 'catch' all exceptions and process them is by using the Application_Error
method. So I tried the following implementation of Application_Error
in the MvcApplication
class:
Application_Error
Application_Error
MvcApplication
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
if(ex is UnauthorizedAccessException)
{
Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
}
}
Problem
The problem here is that although I can debug and see that Response.StatusCode
is being set to 401, the client/browser is still receiving a 500 error.
Response.StatusCode
I expect that there is a very good reason why this is happening, but I have exhausted my brain thinking of search terms that will get me the answer I need.
Question
In short, what do I need to do in order to get the behaviour I am looking for?
For additional information, ultimately what I want is for an UnauthorizedAccessException
to have the same behaviour as how MVC handles unauthenticated requests (which redirects to login page). However, I also need it to work for AJAX requests in that my javascript can check for 401
error and do some specific logic (in which case a response redirect to login page is not workable)
UnauthorizedAccessException
401
1 Answer
1
A clever way to go about doing that is to create a Base Controller that your controllers inherit over the default Controller. There you inherit the default Controller class and override the OnException method.
public abstract class BaseController : Controller
{
protected override void OnException(System.Web.Mvc.ExceptionContext filterContext)
{
var responseCode = Response.StatusCode;
var exception = filterContext.Exception;
switch (exception.GetType().ToString())
{
case "UnauthorizedAccessException":
responseCode = 401;
filterContext.ExceptionHandled = true;
break;
}
Response.StatusCode = responseCode;
base.OnException(filterContext);
}
}
The trick that makes it work is filterContext.ExceptionHandled = true;
if you don't set this to true, the server will return 500.
filterContext.ExceptionHandled = true;
Your controllers will inherit the BaseController
;
BaseController
public class UserController : BaseController
{
public ActionResult Index(){
return View();
}
}
What you'll need to add to this code is your redirect to the login page, to your OnException
method (if needed). I would do it for you but I don't have enough time to write and test it for you.. currently waiting for automated tests to finish.. :-)
OnException
Edit:
I did not realize your view could throw errors too, that obviously won't be handled by the controller.
In this case we can revert to the original Application_Error method on Global.asax.
What we need is two lines of code..
Response.StatusCode = 401;
Response.End();
First line sets the status code to 401,
Second line ends the execution at this point and triggers EndRequest event, so the StatusCode won't be modified to 500.
If you want to attach a message with your response:
Response.Write("Oops, you're not authorized...");
It would be a good idea to call Response.Clear();
before starting to modify the response object inside your Error Handler.
Response.Clear();
Actually, this doesn't catch all exceptions. If an exception is throw from the view page, then it bypasses this method and only goes to
Application_Error
. I am currently looking for a solution, but if you can think of anything of the top of your head it would be appreciated? It specifically coming from the _Layout view– musefan
Jun 29 at 10:12
Application_Error
@musefan Of course, I didn't realize you'll be throwing errors inside views.. Try my edit.
– Adriani6
Jun 29 at 10:32
Sussed it. Because I can be sure AJAX requests will be caught in
OnException
(as you can't AJAX from a view render), then all I need in Application_Error
is to check exception type and then do HttpContext.Current.Response.Redirect("some url");
to handle non-ajax error in view. All my test cases seem to pass now :)– musefan
Jun 29 at 11:55
OnException
Application_Error
HttpContext.Current.Response.Redirect("some url");
Might be a chance for some extra rep here if you happen to know the solution!
– musefan
Jun 29 at 14:15
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Conveniently, I already had a base controller that I was using. Made some changes to handle ajax requests differently, done some testing, and everything appears to be working as desired. Thanks for your time and answer!
– musefan
Jun 29 at 8:43