Try to stay on the page when expected exception was thrown - asp.net-mvc

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.

Related

Return to original view from MVC action filter

Im working on a asp.net core website and im trying to make som global validation exception handling using Filters. The backend can at random places throw fluentapi ValidationException and I want to catch these and show the error messages to the user. This filter only cares about ValidationExceptions. All other exceptions will be handled later..
Instead of using a try/catch in every post action in all my controllers, I want to use a filter that catches only ValidationExceptions, add the errors to the ModelState and then return to the original view with the updated ModelState.
I have tried many things but every time I just get a blank page after the filter finishes. I can easily set a new RedirectToRouteResult witht the controller and action from the context. But then I dont have the ModelState and values the user entered..
public class PostExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is FluentValidation.ValidationException)
{
var ex = context.Exception as FluentValidation.ValidationException;
context.Exception = null;
context.HttpContext.Response.StatusCode = 200;
context.ExceptionHandled = true;
foreach (var item in ex.Errors.ToList())
{
context.ModelState.AddModelError(item.PropertyName, item.ErrorMessage);
}
// Done with the stuff I want.
// Now please go back to the original view with the updated modelstate and values
}
else if (context.Exception is UnauthorizedAccessException)
{
// Do something else...
}
else
{
// Do something else...
}
base.OnException(context);
}
}
You cannot access the particlar Model(related to Action Method) in Exception Filters. So you have to handle the error at Controller level if you want to add Errors to model.
try
{
//Do something
}
Catch(Exception e)
{
ModelState.AddModelError(string key, string errorMessage);
Return View(model)
}
The error message will present itself in the <%: Html.ValidationSummary() %> in your View
Without try-catch blocks you won't know if exception occured in Action Method, So that you can add Custom Errors to Model.

trapping an exception using IExceptionFilter and redirecting permanently binds the path to error page

I am trying to redirect unhandled exceptions to an error page by first generating a crash code and then redirecting the user to the error page. The problem is that after the first time it happens, the original url/action is permanently mapped to the error page url and doesn't even enter the OnException method anymore. Even after I fix the cause of the original exception, the url/action is still mapped to just redirect to the error page. Not sure where this is happening or how to fix it. Below is the code:
public class UnhandledExceptionFilter : IExceptionFilter
{
protected static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public void OnException(ExceptionContext filterContext)
{
var ex = filterContext.Exception;
if (ex != null)
{
string user = "";
try
{
user = Managers.UserManager.FindByUsername(filterContext.HttpContext.User.Identity.Name, true).FullName;
}
catch
{
}
var url = filterContext.HttpContext.Request.Url.ToString();
var urlRef = filterContext.HttpContext.Request.UrlReferrer.PathAndQuery;
var randStr = StringExtensions.GenerateRandomAlphaNumerics(new System.Random(), 4);
Logger.Error(ex, "crashCode={0} - user={1} - url={2} - urlRef={3}", randStr, user, url, urlRef);
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.ExceptionHandled = true;
filterContext.Result = new RedirectResult("/misc/whoops?crash_code=" + randStr, true);
}
}
}
The problem should be solved by not passing true to the second parameter of RedirectResult. Pass false instead:
filterContext.Result = new RedirectResult("/misc/whoops?crash_code=" + randStr, false);
Passing true as the second parameter indicates the redirect is permanent. Which is, based on your question, exactly want you do not want.
See: MSDN RedirectResult and
301 vs 302 redirects

How to handle errors in my CustomAutorize attribute in asp.net 3.0 Application

I am working on an asp.net MVC 3.0 Application. I am using using my own CustomRoleProvider
and CustomErrorHandler by overriding default attributes.
Every thing is working fine. But ,the problem is with the exception handling.
While testing the application , tester has given invalid DB connection to test.
The result is , Custom Error Handler is not rendering Error View , instead it is routing the original path
For ex:
I am running my application as
Home/Index
It is first hitting Custom Role Provider to fetch the roles for the application
Since , the Db Connection is not correct , it is raising exception that "Not able to Connect"
Now , Instead of routing to Error View along with this error message. It is routing to Home Controller and Index action.
**The code for my Custom Error Handler is as Follows**
public class CustomHandleErrorAttribute : HandleErrorAttribute // Error handler
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
{
return;
}
if (new HttpException(null, filterContext.Exception).GetHttpCode() != 500)
{
return;
}
if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
{
return;
}
// if the request is AJAX return JSON else view.
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
filterContext.Result = AjaxError(filterContext.Exception.Message, filterContext);
}
else
{
filterContext.ExceptionHandled = true;
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
};
}
}
protected JsonResult AjaxError(string message, ExceptionContext filterContext)
{
if (String.IsNullOrEmpty(message))
message = "Something went wrong while processing your request. Please refresh the page and try again.";
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
return new JsonResult { Data = new { ErrorMessage = message }, ContentEncoding = System.Text.Encoding.UTF8, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
In the above code , after setting up filterContext.Result . It is not rendering Error View as Expected.
Please correct/suggest me, where i am going wrong..
Updated:
public class CustomRoleProvider : RoleProvider // Custom role provider
{
public override string[] GetRolesForUser(string username)
{
// Fetching roles for user from database
}
// Some other Methods
}
This is method is generating exception , since it is trying to connect to wrong connection
Updated2:
1) I am using Custom Error Handler for the entire controller.
2) I need to catch all the exceptions including Ajax Errors
3) I have included my code for Custom Error Handler Above
4) I am also using CustomRole Provider for entire controller
5) Here, I am trying to generate exception , by giving wrong database connection
6) I am running the URL : Home/Index
7) Before going to thatr URL, it is hitting the methods in Role Provider class since i am using it as a attribute
8) Since, i have gave wrong DB Connection , It is generating exception
9) Then, it fires on exception method of Custom error handler
10) Building the Error Model for the error view
11) But, here is the problem. Instead of rendering Error View , it is going to index method of the Home Controller.
12) But, i need Error View to be rendered here, because it has failed to connect to database and getting roles . I want furthuer execution of URL Home/Index to be stopped here.
Hope this clarifies the problem..i am running in to. please feel free to ask me for furthuer details/Clarification
HandleError is designed to be able to register multiple filters (for example for different exceptions). One filter can handle only some specific exceptions or error cases and another unhandle cases can be handled by another HandleError. I suppose that currently both standard and your [CustomHandleError] filter are applied. You can set the Order property to an integer value that specifies a priority from -1 (highest priority) to any positive integer value. The greater the integer value is, the lower the priority of the filter is. You can use Order parameter for example (see here) to make your filter working before. More full description of the order you can find in the MSDN documentation.
The answer, this one and the article for example provide small examples of usage Order property of HandleError.

How to access data after redirect

Again there are multiple articles which says how to access data after redirect. but doesn't serves my purpose.
I am having errorcontroller which is having index action method and error index view.
If there is any error in the application it will caught in Application_Error event.
inside Application_Error event I had logged the error and redirected to Error Index page like this -
protected new void Application_Error(object sender, EventArgs e)
{
Exception error = Server.GetLastError();
log.error(error.Message);
HttpContext.Current.Response.Redirect("~/Error/Index");
}
Now in the error index view, I would like to display the error message. What should I do in Application_Error event which can be access by Error Index view?
Updated : I don't want to use Session as session object may not be available in Application_Error event. this is dependent on when the error occurred.
Approach - 1
As per my knowledge you can use TempData to store the posted data. It is like a DataReader Class, once read, Data will be lost. So that stored data in TempData will become null.
var Value = TempData["keyName"] //Once read, data will be lost
So to persist the data even after the data is read you can Alive it like below
var Value = TempData["keyName"];
TempData.Keep(); //Data will not be lost for all Keys
TempData.Keep("keyName"); //Data will not be lost for this Key
TempData works in new Tabs/Windows also, like Session variable does.
You could use Session Variable also, Only major problem is that Session Variable are very heavy comparing with TempData. Finally you are able to keep the data across Controllers/Area also.
Approach - 2
This works for me. This is very easy and no need to consider any change in Web.Config or Register the Action Filter in Global.asax file.
ok. So, First I am creating a simple Action Filter. This will handle Ajax and Non Ajax requests.
public class MyCustomErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
var debugModeMsg = filterContext.HttpContext.IsDebuggingEnabled
? filterContext.Exception.Message +
"\n" +
filterContext.Exception.StackTrace
: "Your error message";
//This is the case when you need to handle Ajax requests
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
error = true,
message = debugModeMsg
}
};
}
//This is the case when you handle Non Ajax request
else
{
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Error";
routeData.DataTokens["area"] = "app";
routeData.Values["exception"] = debugModeMsg;
IController errorsController = new ErrorController();
var exception = HttpContext.Current.Server.GetLastError();
var httpException = exception as HttpException;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (System.Web.HttpContext.Current.Response.StatusCode)
{
case 504:
routeData.Values["action"] = "Http404";
break;
}
}
var rc = new RequestContext
(
new HttpContextWrapper(HttpContext.Current),
routeData
);
errorsController.Execute(rc);
}
base.OnException(filterContext);
}
}
Now you can implement this Action Filter on Controller as well as on the Action only.Example:
I am going little off topic. I thought this is bit important to explain.
If you pay attention to the above highlighted part. I have specified the order of the Action Filter. This basically describes the order of execution of Action Filter. This is a situation when you have multiple Action Filters implemented over Controller/Action Method
This picture just indicates that let's say you have two Action Filters. OnActionExecution will start to execute on Priority and OnActionExecuted will start from bottom to Top. That means in case of OnActionExecuted Action Filter having highest order will execute first and in case of OnActionExecuting Action Filter having lowest order will execute first. Example below.
public class Filter1 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will start here - 1
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 5
base.OnActionExecuted(filterContext);
}
}
public class Filter2 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will move here - 2
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 4
base.OnActionExecuted(filterContext);
}
}
[HandleError]
public class HomeController : Controller
{
[Filter1(Order = 1)]
[Filter2(Order = 2)]
public ActionResult Index()
{
//Execution will move here - 3
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
}
You may already aware that there are different types of filters within MVC framework. They are listed below.
Authorization filters
Action filters
Response/Result filters
Exception filters
Within each filter, you can specify the Order property. This basically describes the order of execution of the Action Filters.
Use TempData for getting value.
Some feature about TempData
TempData is a dictionary object that is derived from TempDataDictionary class and stored in short lives session.
TempData is used to pass data from current request to subsequent request means incase of redirection.
It’s life is very short and lies only till the target view is fully loaded.
It’s required typecasting for complex data type and check for null values to avoid error.
It is used to store only one time messages like error messages, validation messages.

Error Handling in ASP.NET MVC

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

Resources