I am working with MVC4.
When I cause an exception the information is shown in the browser.
How can I get the exception information in an exception assistant instead?
Thanks for any suggestions.
You can use Exception Handling in Global.asax:
protected void Application_OnError( )
{
var exception = Server.GetLastError( );
//handle exception here
}
You can also use Exception filters:
There is a method 'RegisterGlobalFilters()' having 'filters.Add(new HandleErrorAttribute())' filter that deals with all the errors in MVC application. The method 'RegisterGlobalFilters()' called by 'Application_Start()' method.
public static void RegisterGlobalFilters(GlobalFiltersCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
Error View
The Error view that is created by default contains the following HTML:
Collapse | Copy Code
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Error</title>
</head>
<body>
<h2>
Sorry, an error occurred while processing your request.
</h2>
</body>
</html>
Accessing the exception details in Error view:
public class HandleErrorInfo
{
public HandleErrorInfo(Exception exception, string controllerName,
string actionName);
public string ActionName { get; }
public string ControllerName { get; }
public Exception Exception { get; }
}
Check this link for further information :
Exception Handling in ASP.NET MVC
PD:
Set in web.Config:
<CustomErrors mode="Off">
to stop watching the exception in browser
Create an Error Controller with several methods (each method is an error) :
public Class Error : Controller
{
public ActionResult ErrorMethodName()
{
return View();
}
}
In each ErrorName.cshtml, write informations about your error.
Now for each Method in your other Controllers do this :
public ActionResult MethodeName()
{
try
{
// your code
}
catch (exception)
{
return.RedirectToAction('ErrorMethodName','Error');
}
}
Related
I have a small scenario :
I fetch data from the database and I want to filter it in my ActionExecuted Filter and then send it to the View. I am accessing the fetched data from the Database using TempData, in my Filter. After I modify the data inside my Filter I want to send it to the View.
What are the possible solutions for this ?
When you modify your TempData inside OnActionExecuted method of your custom filter the view will get that modified TempData by design.
Example:
Filter:
public class MyFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.Controller.TempData["key"] += "modified";
}
}
Controller:
[MyFilter]
public class HomeController : Controller
{
public ActionResult Index()
{
TempData["key"] = "some_value";
return View();
}
}
View(Index.cshtml):
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
</head>
<body>
<div>
#TempData["key"]
</div>
</body>
</html>
When you run this you will see Index page showing modified data pulled from TempData.
Here is some article explaining action filters feature.
So basically I have an Custom Authorize attribute and at some point it throwns an exception. I want to catch the exception and display the message to the user.
this is the contoller
[HandleError]
public class TestController : BaseController
{
[CustomAuthorize("someperm, someperm2")]
public ActionResult GamblerUser()
{
return View();
}
}
this is the code in FilterConfig
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
}
this is the code in global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
and the web.config
<customErrors defaultRedirect="~/Shared/Error" mode="On">
</customErrors>
and finally the view itself
#model HandleErrorInfo
#{
ViewBag.Title = "Error";
}
<h2>Sorry Error Occured</h2> <br />
<h2>#Model.Exception.Message</h2>
and this is the message I get whenever the exception occurs.
Runtime Error
Description: An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated.
I know that I am missing something very basic but this thing is just giving me headache.
I've look at tons of examples I just cant get this thing working.
ViewBag is null. I would use a static title in the html or find a way around it like this.
I'm trying something new (to me) in using an abstract base class for my layout viewmodel.
The problem is that when I run the site as is, it throws a very cryptic (to me) exception. What does this exception mean, and what might I do to resolve it?
Layout
#model MyApp.Core.ViewModels.LayoutViewModel
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>#Model.Title</title>
</head>
<body>
<div>
#RenderBody()
</div>
</body>
</html>
Index
#model MyApp.Core.ViewModels.Home.IndexViewModel;
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>#Model.Body</h1>
LayoutViewModel
namespace MyApp.Core.ViewModels
{
public abstract class LayoutViewModel
{
public string Title { get; set; }
}
}
IndexViewModel
namespace MyApp.Core.ViewModels.Home
{
public class IndexViewModel : LayoutViewModel
{
public string Body { get; set; }
}
}
Controller
[HttpGet]
public ActionResult Index()
{
var model = new IndexViewModel
{
Title = "Hello World",
Body = "Hello World"
};
return View(model);
}
And the Exception
Compilation Error Description: An error occurred during the
compilation of a resource required to service this request. Please
review the following specific error details and modify your source
code appropriately.
Compiler Error Message: CS1003: Syntax error, '>' expected
Source Error:
Line 27:
Line 28:
Line 29: public class _Page_Views_Home_Index_cshtml :
System.Web.Mvc.WebViewPage<FutureStateMobile.Core.ViewModels.Home.IndexViewModel;>
{
Line 30:
Line 31: #line hidden
Source File: c:\Users\Chase\AppData\Local\Temp\Temporary ASP.NET
Files\root\b314e0d7\36f522db\App_Web_index.cshtml.a8d08dba.yr7oemfz.0.cs
Line: 29
Compare and contrast:
Layout
#model MyApp.Core.ViewModels.LayoutViewModel
Index
#model MyApp.Core.ViewModels.Home.IndexViewModel;
Got it yet? Here's the answer:
one of them has a ; which shouldn't be there
I just add the line twice, and deleted... problem solved!
#model MyApp.Core.ViewModels.LayoutViewModel
#model MyApp.Core.ViewModels.Layout_OrOther_Model
Try to compile, you get an error (only one model blah, blah..)
Delete one of them.
#model MyApp.Core.ViewModels.LayoutViewModel
Compile.
That works for me!
bad:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<DSNY.Core.Interfaces.IUser>" %>
versus
good:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<DSNY.Core.Interfaces.IUser>>" %>
Compiler kept telling me it was expecting an extra >.
I have implemented exception handling using below code.[Edited Start] I do not know why it is calling view twice. [Edited Done]
Basecontroller
public class BaseController : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.HttpContext.IsCustomErrorEnabled)
{
filterContext.ExceptionHandled = true;
if (filterContext.Exception.GetType() == typeof(ArgumentOutOfRangeException))
{
this.View("OutOfRange").ExecuteResult(this.ControllerContext);
}
else
{
this.View("Error").ExecuteResult(this.ControllerContext);
}
}
base.OnException(filterContext);
}
}
HomeController
public class HomeController : BaseController
{
public ActionResult Exception2()
{
throw (new ArgumentOutOfRangeException());
}
public ActionResult Exception3()
{
throw (new Exception());
}
}
Error View (Shared folder only)
#model System.Web.Mvc.HandleErrorInfo
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h2>
Sorry, an error occurred while processing your request.
</h2>
<h3>
#if (Model != null)
{
<p>#Model.Exception.GetType().Name<br />
thrown in #Model.ControllerName #Model.ActionName</p>
<br />
#Model.Exception
}
</h3>
</body>
</html>
OutOfRange view (Shared folder only)
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>OutOfRange Exception</title>
</head>
<body>
<div>
<h3>
#if (Model != null)
{
<p>#Model.Exception.GetType().Name<br />
thrown in #Model.ControllerName #Model.ActionName</p>
<br />
#Model.Exception
}
</h3>
</div>
</body>
</html>
Execution URL : http://[domainName]/Home/Exception2
Here it is working fine. [EDITED : Here it also call twice.]
Execution URL : http://[domainName]/Home/Exception3
Here it is not working fine. You can see that "Sorry, an error occurred while processing your request." is coming twice. When I debugged the application using above URL, Error view called twice ( First time Model is null and second time Model contains some value). May I know what is wrong with my implementation?
Things to try:
Remove the default HandleError global action filter attribute from your Global.asax if it is present. When you create a new ASP.NET MVC 3 application the default template registers it inside the RegisterGlobalFilters method. So comment out the line which registers this attribute.
Enable custom errors in your web.config:
<customErrors mode="On" />
UPDATE:
If you want to pass a model to the Error.cshtml view (as it is strongly typed to HandleErrorInfo) you could do the following:
else
{
string controllerName = filterContext.RouteData.GetRequiredString("controller");
string actionName = filterContext.RouteData.GetRequiredString("action");
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = "Error",
ViewData = new ViewDataDictionary<HandleErrorInfo>(model)
};
filterContext.Result = result;
}
Add base.OnException(filterContext); before
if (filterContext.HttpContext.IsCustomErrorEnabled)
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
if (filterContext.HttpContext.IsCustomErrorEnabled)
}
I had the same problem, but I managed to resolve the error by adding:
Response.End();
To the end of my OnException method.
I know it's not the ideal resolution, but, it did at least get me out of a bind until such a time as I can investigate a more complete option.
When implementing error-handling using the built-in validation-helpers on a strongly-typed view, you usually create a try/catch block within the controller and return a view with it's corresponding model as a parameter to the View() method:
The controller
public class MessageController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Models.Entities.Message message)
{
try
{
// Insert model into database
var dc = new DataContext();
dc.Messages.InsertOnSubmit(message);
dc.SubmitChanges();
return RedirectToAction("List");
}
catch
{
/* If insert fails, return a view with it's corresponding model to
enable validation helpers */
return View(message);
}
}
}
The view
<%# Page
Language="C#"
Inherits="System.Web.Mvc.ViewPage<Models.Entities.Message>" %>
<%= Html.ValidationSummary("Fill out fields marked with *") %>
<% using (Html.BeginForm()) { %>
<div><%= Html.TextBox("MessageText") %></div>
<div><%= Html.ValidationMessage("MessageText", "*") %></div>
<% } %>
I've implemented a simple error-handler in the form of an ActionFilterAttribute, which will be able to either redirect to a generic error view, or redirect to the view which threw an exception, and let the validation-helpers spring to life.
Here's how my ActionFilterAttribute looks:
public class ErrorLoggingAttribute : ActionFilterAttribute, IExceptionFilter
{
private Boolean _onErrorRedirectToGenericErrorView;
/// <param name="onErrorRedirectToGenericErrorView">
/// True: redirect to a generic error view.
/// False: redirect back the view which threw an exception
/// </param>
public ErrorLoggingAttribute(Boolean onErrorRedirectToGenericErrorView)
{
_onErrorRedirectToGenericErrorView = onErrorRedirectToGenericErrorView;
}
public void OnException(ExceptionContext ec)
{
if (_onErrorRedirectToGenericErrorView)
{
/* Redirect back to the view where the exception was thrown and
include it's model so the validation helpers will work */
}
else
{
// Redirect to a generic error view
ec.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "Error"},
{"action", "Index"}
});
ec.ExceptionHandled = true;
}
}
}
Redirecting to the view which threw the exception is fairly simple. But here's the kicker: In order for the validation helpers to work, you need to provide the view with it's model.
How would you return the view which threw an exception and provide the view with it's corresponding model? (In this case Models.Entities.Message).
I got it to work!
For some odd reason, all I needed to do was to pass on the ViewData to a new ResultView.
Here's the complete code:
public class ErrorLoggingAttribute : ActionFilterAttribute, IExceptionFilter
{
private String _controllerName, _actionName;
private Boolean _redirectToGenericView = false;
public ErrorLoggingAttribute()
{
}
public ErrorLoggingAttribute(String actionName, String controllerName)
{
_controllerName = controllerName;
_actionName = actionName;
_redirectToGenericView = true;
}
void IExceptionFilter.OnException(ExceptionContext ec)
{
// log error
if (_redirectToGenericView)
{
ec.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", _controllerName},
{"action", _actionName}
});
}
else
{
ec.Result = new ViewResult
{
ViewName = ((RouteData) ec.RouteData).Values["action"].ToString(),
TempData = ec.Controller.TempData,
ViewData = ec.Controller.ViewData
};
}
ec.ExceptionHandled = true;
}
}
Usage
Here's how you would use the attribute on a controller-action, to redirect to the same view (with it's associated model) to enable standard validation-helpers to kick in, when an exception occurs:
[ErrorLogging]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Models.Entities.Message message)
{
var dc = new Models.DataContext();
dc.Messages.InsertOnSubmit(message);
dc.SubmitChanges();
return RedirectToAction("List", new { id = message.MessageId });
}
And here's how you would use the attribute, to redirect to a generic view, when an exception occurs:
[ErrorLogging("ControllerName", "ViewName")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Models.Entities.Message message)
This is a complete separation of logic. Nothing in the controller but the very basics.
Since you inherit from ActionFilterAttribute From OnActionExecuting, you can grab your model.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.Controller.ViewData.Model as YourModel;
...
}
But there is already HandleError defined in MVC system, why don't you use this one instead of baking your own.
I suggest you read this blog on this issue.
If your action throws exception, there's no way to pass the model to the view since the model is not probably created yet - or not fully created. That's probably why the result is null. You can't rely on the data after exception was thrown.
But you can pass pass "default" model to your action filter like this:
[ErrorLogging(new EmptyModel())]
// or to create using Activator
[ErrorLogging(typeof(EmptyModel))]
// or even set view name to be displayed
[ErrorLogging("modelerror", new EmptyModel())]
This way your filter will pass this "error model" that you explicitely set to be displayed when an error happened.
public class MessageController : Controller
{
public ActionResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( Message message )
{
try
{
// Exceptions for flow control are so .NET 1.0 =)
// ... your save code here
}
catch
{
// Ugly catch all error handler - do you really know you can fix the problem? What id the database server is dead!?!
return View();
}
}
}
The details of the model are already present in modelstate. Any errors should also already be present in modelstate. Your exception handler only needs to handle the case where you want to redirect to a generic error page. Better / more obvious is to throw the attribute away and if you want to redirect in the catch, return a redirect result.