Error handling last chance to catch View Rendering exception - asp.net-mvc

I've got ErrorController which customly handles my website errors.
It's pretty standard:
public class ErrorController : BaseController
{
public ActionResult Error404(Exception ex)
{
return View();
}
public ActionResult Error500(Exception ex)
{
return View();
}
}
However, in case if some rendering exception occurs inside of the View code (and this might occur, as the page has Master page (master layout) and different might happen), then I am not able to catch that rendering exception.
I can really see that exception with implementing ActionFilterAttribute.OnResultExecuted:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Exception != null)
{
// not sure what to do here
} else base.OnResultExecuted(filterContext);
}
but in that case MVC looks for ~/Shared/Error.cshtml (incl. this path) after that exception occurs, and I can't provide the Errors view rendering exception to the user -- the "Last chance exception".
Is there any way to handle that?

Here is a nice article on Exception handling in ASP.Net MVC that should help
Method 4:- Inheriting from “HandleErrorAttribute”
public class CustomHandleErrorAttribute: HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
Exception ex = filterContext.Exception;
filterContext.ExceptionHandled = true;
var model = new HandleErrorInfo(filterContext.Exception, "Controller", "Action");
filterContext.Result = new ViewResult()
{
ViewName = "Error",
ViewData = new ViewDataDictionary(model)
};
}
}
And you attach that to your base controller.

Related

Return shared view from ViewComponent

I have an Error-view which should be loaded if an Exception occurs. The Error view is located in:
Views/Shared/Error.cshtml (See attached picture).
In my Controller, my try-and catch looks like this:
public IActionResult Device(string id, bool like, int type)
{
try
{
//code
return View(viewModel);
}
catch (Exception exe)
{
return View("Error", exe);
}
}
This works and the correct Error View is displayed. However, I have a ViewComponent which should display the same Error-view.
I have tried the following:
1) Copy the Error-file and pasted it in the same folder as my ViewComponent (Right beneath the Default view). This does not give me an error, but the Default-view is the one being loaded.
2) I have returned the Error view from the shared-folder in the following way:
return View("../../../Shared/Error");
This as well does not give errors, but the Default view is the one being loaded.
Any ideas on how to solve this?
EDIT
So far I have created a new class
public class HandleExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var result = new ViewResult { ViewName = "Error" };
var modelMetadata = new EmptyModelMetadataProvider();
result.ViewData = new ViewDataDictionary(
modelMetadata, context.ModelState);
result.ViewData.Add("HandleException",
context.Exception);
context.Result = result;
context.ExceptionHandled = true;
}
}
And in my Error View I added this:
#{
ViewData["Title"] = "Error";
Layout = "_LayoutCustomer";
Exception ex = ViewData["HandleException"] as Exception;
}
And lastly, I added [HandleException] on top of my Controller:
[HandleException]
public class CustomerController : Controller
{
//All the actions...
}
To simulate a new Exception, I use:
public IActionResult Device(string id, bool like, int type)
{
try
{
throw new Exception();
//code
return View(viewModel);
}
catch (Exception exe)
{
throw;
}
}
This seems to work in the Controller-actions. How can I simulate if it works within the ViewComponent? It gives me an error when I do the same try-catch method.
Step 1 : - Create a Custom ExpectionFilter Attribute
public class CustomExpectionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Shared/Error.cshtml"
};
}
}
Step 2 :- Register CustomExpectionFilter in FilterConfig
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomExpectionFilter());
}
}
Step 3 :- Change on
public IActionResult Device(string id, bool like, int type)
{
try
{
//code
return View(viewModel);
}
catch (Exception)
{
throw;
}
}
Now when ever error occur in application it will be calling CustomExpection Filter and this filter will handle error and display Error page.

Automatic C# MVC Error Handling

I've got this code.
public ActionResult Index()
{
ReceiptModel model = new ReceiptModel();
try
{
model = new ReceiptModel(context);
}
catch (BussinessException bex)
{
ModelState.AddModelError("Index", bex.MessageToDisplay);
return View("Index");
}
return View(model);
}
BussinesException ir returned from database and then displayed for user. I have to put on every controller method try-catch statement, which is a bit tedious. Is there any easier way how to handle these exceptions?
P.S. All other exceptions are handled with HandleExceptionAttribute
UPDATE:
I used Floradu88 approach. So Now i have something like this.
public sealed class HandleBussinessExceptionAttribute : HandleErrorAttribute, IExceptionFilter
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.Controller.TempData["UnhandledException"] = filterContext.Exception;
filterContext.ExceptionHandled = true;
((Controller)filterContext.Controller).ModelState.AddModelError(
((BussinessException)filterContext.Exception).Code.ToString(),
((BussinessException)filterContext.Exception).MessageToDisplay
);
filterContext.Result = new ViewResult
{
ViewName = this.View,
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData,
};
}
}
and on Controller action i put
[HandleBussinessExceptionAttribute(Order = 2, ExceptionType = typeof(BussinessException), View = "Login")]
i also tried in exception handler:
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(filterContext.RouteData));
and then handle error in action with ModelState.IsValid but values to action comes null. So for now i use first approach. When i have a bit more time i'll try to fix second approach.
Please read the documentation on this part:
http://msdn.microsoft.com/en-us/library/gg416513%28v=vs.98%29.aspx
http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs
Too much content to be posted here:
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
And your controller:
public class ProductsController : ApiController
{
[NotImplExceptionFilter]
public Contact GetContact(int id)
{
throw new NotImplementedException("This method is not implemented");
}
}
According to this post, Create a custom binder and put the model in TempData:
public class AppBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
controllerContext.Controller.TempData["model"] = bindingContext.Model;
}
}
Register it in global.asax:
ModelBinders.Binders.DefaultBinder = new AppBinder();
Create a BaseController and override the OnException:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
this.ModelState.AddModelError(string.Empty, filterContext.Exception.Message);
filterContext.Result = new ViewResult
{
ViewName = filterContext.RouteData.Values["action"].ToString(),
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData
};
base.OnException(filterContext);
}
All Actions int The Controllers which inherited from this base controller will show their unhandled exceptions their own view in validation summery (remember to have
#Html.ValidationSummary(true)
in page). it works for me, hope works for you too!

Return special JsonResult in case of Exception occurs

public JsonResult Menu() { // Exception }
I need application not to redirect user to the 404 page, but return special JSON result like { "result":1 }.
I wonder, is there any another solution, not try-catching.
You can implement your own FilterAttribute similar to the HandleErrorAttribute.
The HandleErrorAttribute normally does a redirect when an error occurs, but you could implement a similar attribute that returns a JsonResult. Something like the following will do:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
filterContext.Result = new JsonResult
{
Data = new { result = 1 },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.ExceptionHandled = true;
}
}
And then
[CustomHandleError]
public JsonResult Menu()
{
throw new Exception();
}
I would recommend that you download the MVC source code from CodePlex and inspect the current implementation of HandleErrorAttribute. It is a lot more subtle than my crude implementation above and you may want some of its functionality.

replacement for try catch(MyEx ex) in each action

I need something that would work like this:
public ActionResult Ac()
{
try {
//stuff...
}
catch(MyException ex)
{
//handle
}
}
but without putting try catch in each action method
You want to annotate your classes with HandleErrorAttribute - http://msdn.microsoft.com/en-us/library/system.web.mvc.handleerrorattribute.aspx.
If the functionality of the built in handler above isn't sufficient then you can define your own class which implements IExceptionFilter - the OnException method takes an ExceptionContext object with Result and HttpContext properties you can use to control the outcome, something like:
public class MyHandleErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
Exception e = filterContext.Exception;
// Do some logging etc. here
if (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled)
{
ViewResult lResult = ...
filterContext.Result = lResult;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
Use Exception Filters for exception handling.
How about
[HandleError(ExceptionType = typeof(MyException ), View = "MyErrView"))]
public ActionResult Ac()
{
//stuff
}
but with a custom HandleError Attribute that handles the type of exceptions you are targeting. This SO question should give you a good start.

Transaction exception problem - s#arp architecture

I am deleting things like this:
[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
public RedirectToRouteResult DeleteQualitativeGlobalFeatureValue(string Id)
{
try
{
BlaService.DeleteBla(Id);
}
catch (Exception e)
{
ModelState.AddModelError("Exception", e.Message);
}
return RedirectToAction("Bladibla", new { Id = FeatureId });
}
However, if something is ’illegally’ deleted (e.g. causing the violation of a referential constraint) I get a horrible exception which is not caught by my try catch block. I presume this has to do with the [Transaction] attribute. How can I avoid this to catch ANY exceptions in the controller method?
Thanks.
Best wishes,
Christian
This is because actual commit and database-side validation happens on transaction commit.
You can use your own, slightly modifed version of the Sharp attribute.
public class TransactionAttribute: ActionFilterAttribute
{
private TransactionAttributeHelper helper = new TransactionAttributeHelper();
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
helper.BeginTransaction();
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
try
{
// notice that I rollback both on exception and model error, this helps a lot
helper.FinishTransaction(filterContext.Exception == null &&
filterContext.Controller.ViewData.ModelState.IsValid);
}
catch (Exception ex)
{
// here add ModelError, return error, or redirect
}
}
}
TransactionAttributeHelper is placed to .Data assembly to avoid NHibernate reference in .Controllers.
public class TransactionAttributeHelper
{
public void BeginTransaction()
{
NHibernateSession.CurrentFor(GetEffectiveFactoryKey()).BeginTransaction();
}
public void FinishTransaction(bool commit)
{
string effectiveFactoryKey = GetEffectiveFactoryKey();
ITransaction currentTransaction =
NHibernateSession.CurrentFor(effectiveFactoryKey).Transaction;
if (currentTransaction.IsActive)
{
if (commit)
{
currentTransaction.Commit();
}
else
{
currentTransaction.Rollback();
}
}
}
private static string GetEffectiveFactoryKey()
{
return NHibernateSession.DefaultFactoryKey;
}
}
Alternatively, of course, you can do transations without the attribute using repository.DbContext.BeginTransaction/Commit/etc methods and catch/process errors manually. But the above approach saves from a lot of such manual work.
You should look into an attribute that implements the IExceptionFilter interface. For example the System.Web.Mvc.HandleErrorAttribute can display an alternate view for an exception and gives that view access to the exception. You can also create your own attributes that implement IExceptionFilter if you want to handle things differently or log the exception using log4net or Elmah.
Having an IExceptionFilter attribute on the method will catch the exception even if the exception occurs in the TransactionAttribute's code.

Resources