I am using ASPNet MVC4 with HandleErrorAttribute filter configured on Global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
On my web.config I have configured customError mode to RemoteOnly:
<customErrors mode="RemoteOnly" />
So, the user are redirected to ~/Shared/Error.cshtml when a exception are raised on any View, that`s ok.
Now, I can catch this exception for log:
~/Shared/Error.cshtml code:
#{
log4net.LogManager
.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
.Fatal("Not captured exception", ex);
}
Actually this code are working fine (without EX parameter), but I need to improve my log including exception details.
How I can get exception details on this page?
Just make your Error.cshtml view strongly typed to HandleErrorInfo which is the model being passed to this view by the HandleErrorAttribute:
#model System.Web.Mvc.HandleErrorInfo
#{
log4net
.LogManager
.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
.Fatal("Not captured exception", Model.Exception);
}
And by the way, logging inside a view doesn't seem like the cleanest thing you could do. I'd rather write my custom error handler attribute and log the exception there.
I'd recommend writing a custom handler and log within, e.g.
[AttributeUsage(AttributeTargets.All, Inherited = true)]
public class HandleErrorAttributeCustom : HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
base.OnException(context);
log4net.LogManager
.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
.Fatal("Not captured exception", context.Exception);
}
}
Related
In MVC Web Forms, we have the error Filter by impementing IExceptionFilter. This ensures that any exception in the application will be routed here.
Is there an equivalent for this in ASP.Net? Is the Application_Error method invoked for all exceptions?
First, you should check through the official documentation.
You're asking about Web Forms, and then referring to ASP.NET, so I'm not really sure if you're asking about MVC or something else, but I am going to assume MVC. 1 & 3 apply regardless.
I found a nice Stackify article that is more condensed, which I'll paraphrase here. I'm going to ignore error handling that is not a global solution (HandleErrorAttribute, etc).
Depending on your needs, there are a few options for dealing with unhandled exceptions:
1) Generic Erorr pages configured in Web.Config section
<system.web>
<customErrors mode="On" defaultRedirect="~/ErrorHandler/Index">
<error statusCode="404" redirect="~/ErrorHandler/NotFound"/>
</customErrors>
<system.web/>
If you set up something similar to the code above (replacing the example paths with paths to your error handler or other web pages), you can set up a default error page, as well as error pages for each type of status code. If you set mode="RemoteOnly", you'll only see these custom error pages when not on localhost, which can be very useful for debugging.
For example, I have the following in my project:
<customErrors mode="RemoteOnly" defaultRedirect="/ErrorHandler">
<error statusCode="400" redirect="~/ErrorHandler/?errorCode=400" />
<error statusCode="401" redirect="~/ErrorHandler/?errorCode=401" />
<error statusCode="403" redirect="~/ErrorHandler/?errorCode=403" />
<error statusCode="404" redirect="~/ErrorHandler/?errorCode=404" />
<error statusCode="500" redirect="~/ErrorHandler/?errorCode=500" />
</customErrors>
This means that any generic exception with those status codes is automatically handles for me. ErrorHandler is a simple controller I created for this. However, this does not allow you to execute code on an unhandled exception, just display a generic error page.
2) Override OnException function on Controller
This requires you to set up an interface class for your controllers and to develop an override method for the controller's OnException method.
From the Stackify article, they develop a class named UserMvcController that inherits from Controller. I personally have a class named IBaseController that has this method, along with other methods, overridden.
An example of a simple interface:
public class UserMvcController : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
//Log the error!!
_Logger.Error(filterContext.Exception);
//Redirect or return a view, but not both.
filterContext.Result = RedirectToAction("Index", "ErrorHandler");
// OR
filterContext.Result = new ViewResult
{
ViewName = "~/Views/ErrorHandler/Index.cshtml"
};
}
}
An example from one of my projects with a few other functions. All of these functions are available on your controllers if your controllers inherit from your base class:
public class IBaseController : Controller
{
// Function to set up modal pup with Error styling
public void Error(string errorHeader, string errorMessage)
{
ViewBag.ErrorHeader = errorHeader;
ViewBag.ErrorMessage = errorMessage;
ViewBag.JavaScriptHandler = "ShowErrorModal();";
}
// Function to set up modal pup with Informational styling
public void Info(string infoHeader, string infoMessage)
{
ViewBag.InfoHeader = infoHeader;
ViewBag.InfoMessage = infoMessage;
ViewBag.JavaScriptHandler = "ShowInfoModal();";
}
// Function to override OnException method to pass exception info the ErrorController
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
//Redirect or return a view, but not both.
filterContext.Result = RedirectToAction("Message", "ErrorHandler", new { area = "", errorException = filterContext.Exception.Message });
}
}
As you can see, my OnException override simply passes some of the exception information to another controller to handle. I do not use it to log or trace anything. If you're using a controller, this method will be called on all unhandled exceptions.
3) Use Application_Error
In your Global.asax file, you can also set up the Application_Error() function to handle errors.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected void Application_Error()
{
var ex = Server.GetLastError();
//log the error!
_Logger.Error(ex);
}
}
This will allow you to do simple logging and generic types of error handling. I suppose you can implement something very robust here, but I prefer to do most of my logical handling within the controller.
Closing Remark
Personally, I prefer overriding the OnException method of the controller and handling my errors through there. I've found that by using that, and a well developed controller to handle errors, you can do some powerful things. There are even more options than these, but I think this will get you started.
Best.
Basically when my app has an unhandled exception I want it to be recorded as an unhandled exception by IIS (so that it can be seen on Event Viewer, for example) but I also want to route the user to an error controller.
I've overridden the OnException method of my controller in order to route users to a custom error page. The crux of the problem is this code:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.Result = RedirectToAction("GeneralError", "Error", new{ routeValueA = "some value", routeValueB = "some other value"});
filterContext.ExceptionHandled = false;
}
My Problem is this: If I set filterContext.ExceptionHandled = false then I get a yellow screen of death instead of being rerouted to my error handling controller. If i set filterContext.ExceptionHandled = true then I get rerouted, but the exception is not recorded as an unhandled exception.
I know I can set a static error page using the web.config, but I don't want to do this, because then I can't use route values to dynamically send data to my Error Controller.
Can I succesfully set a result to my filterContext.Result without marking filterContext.ExceptionHandled= true?
try following from this source
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled)
{
return;
}
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Shared/Error.aspx"
};
filterContext.ExceptionHandled = true;
}
Or You can even try this too
set custom errors in web.config as below:
<customErrors mode="On" defaultRedirect="~/Error">
<error redirect="~/Error/NotFound" statusCode="404" />
<error redirect="~/Error/UnauthorizedAccess" statusCode="403"/>
</customErrors>
Your ErrorController:
public class ErrorController : Controller
{
public ViewResult Index()
{
return View("Error");
}
public ViewResult NotFound()
{
Response.StatusCode = 404; //you may want to set this to 200
return View("NotFound");
}
public ViewResult UnauthorizedAccess()
{
Response.StatusCode = 404; //you may want to set this to 200
return View("UnauthorizedAccess");
}
}
Register HandleErrorAttribute as a global action filter in the FilterConfig class as follows:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomHandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
UPDATE
I suggest you to read this answer as it gives the complete detail to the question you have asked and I hope you will find a good solution there!!
I believe what you are looking for is Custom Error Pages that you can set in your web.config file and tell IIS to redirect to a custom error page instead of the default (as you called it yellow page of death :) ) for any unhandled exceptions.
This might help :
How to make custom error pages work in ASP.NET MVC 4
I'm trying to get Session enabled in the GettHttpHandler method of my IRouteHandler classes but session is always null. Could someone tell me what I'm doing wrong?
In global.asax I have
RouteTable.Routes.Add("All", new Route("{*page}", new MyRouteHandler()));
The MyRouteHandler class where Session is null looks like this:
public class MyRouteHandler : IRouteHandler, IRequiresSessionState
{
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string test = HttpContext.Current.Session["test"].ToString();
return BuildManager.CreateInstanceFromVirtualPath("~/Page.aspx", typeof(Page)) as Page;
}
}
I made a small test app that shows the problem.
Could someone tell me what I'm doing wrong?
Edited to add:
Yes, I really need session data in the route handler. There are many reasons but one easily explainable is when the user can switch to browse the site in preview mode.
The site consists of a hierarchy of dynamic pages (/page1/page2...) in the database that can be published normally or to preview. A content producer browsing the site can choose to view just normal pages or also those published to preview. The browsing mode is stored in the user's session so therefor the route handler needs to know the browsing mode to be able to solve the requested page.
So I really need the session already at that stage.
I have explained reason behind this problem in this answer. And now I have found a solution to the problem!
You create a custom HttpHandler class:
class MyHttpHandler : IHttpHandler, IRequiresSessionState
{
public MyRequestHandler RequestHandler;
public RequestContext Context;
public MyHttpHandler(MyRequestHandler routeHandler, RequestContext context)
{
RequestHandler = routeHandler;
Context = context;
}
public void ProcessRequest(HttpContext context)
{
throw new NotImplementedException();
}
public bool IsReusable
{
get { throw new NotImplementedException(); }
}
}
It is important to add IRequiresSessionState interface, otherwise IIS does not load session for this request. We do not need to implement logic of ProcessRequest and IsReusable, but class must implement the IHttpHandler interface.
You change your RouteHandler implementation:
public class MyRequestHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHttpHandler(this, requestContext);
}
public IHttpHandler DelayedGetHttpHandler(RequestContext requestContext)
{
// your custom routing logic comes here...
}
}
You simply move your original, Session dependent logic to DelayedGetHttpHandler function and in the GetHttphandler function you return an instance of the helping MyHttpHandler class.
Then, you hook your handling logic to the HttpApplication.PostAcquireRequestState event, e.g. in the Global.asax:
public class Global : HttpApplication
{
public override void Init()
{
base.Init();
PostAcquireRequestState += Global_PostAcquireRequestState;
}
}
For more reference, check this page: https://msdn.microsoft.com/en-us/library/bb470252(v=vs.140).aspx. It explains the request lifecycle and why I use the PostAcquireRequestState event.
In the event handler, you invoke your custom RouteHandling function:
void Global_PostAcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Handler is MyHttpHandler) {
var handler = HttpContext.Current.Handler as MyHttpHandler;
HttpContext.Current.Handler = handler.RouteHandler.DelayedGetHttpHandler(handler.Context);
}
}
And that's it. Works for me.
I am not sure that you can do this (although I may be wrong). My recollection is that IRequiresSessionState indicates that an HttpHandler needs session state, rather than the route handler (which is responsible for giving you a handler appropriate to the route).
Do you really need the session in the route handler itself and not the handler it gives out?
Well I know this is old thread but just putting up the answer here if anyone like me falls in the same scenario, I found an answer here
What you do is just add a runAllManagedModulesForAllRequests="true" attribute to your modules tag in web.config like below
<system.webServer>
.....
<modules runAllManagedModulesForAllRequests="true">
........
</modules>
......
</system.webServer>
However this is not a good solution as it calls managed module everytime, i am using
<remove name="Session" />
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
add it in modules section of web.config, this a better solution than the previous one.
I have AdminError.aspx and Error.aspx pages and I want to display AdminError.aspx when there is an exception. I need both files.
Why this doesn't work?
<customErrors mode="On" redirectMode="ResponseRedirect" defaultRedirect="AdminError.aspx" />
Instead Error.aspx is always displayed.
What can I do?
Asp.net mvc provide [HandleError] attribute to handle this kind of requirement, you can specify different error page (view) you want to redirect to based on specific error type. It's very flexible and recommended to do that way.
For example
[HandleError(ExceptionType = typeof(NullReferenceException),
View = "NullError")]
[HandleError(ExceptionType = typeof(SecurityException),
View = "SecurityError")]
public class HomeController : Controller
{
public ActionResult Index()
{
throw new NullReferenceException();
}
public ActionResult About()
{
return View();
}
}
Check out this similar question to find out more information.
Thanks,
I think I found a solution.
I'm using HandleErrorWithELMAHAttribute (How to get ELMAH to work with ASP.NET MVC [HandleError] attribute?)
and in OnException method I've set my view:
public override void OnException(ExceptionContext context)
{
View = "AdminError"; // this is my view
base.OnException(context);
var e = context.Exception;
if (!context.ExceptionHandled // if unhandled, will be logged anyhow
|| RaiseErrorSignal(e) // prefer signaling, if possible
|| IsFiltered(context)) // filtered?
return;
LogException(e);
}
I've noticed that it works with and without redirectMode and defaultRedirect attributes from customErrors tag.
I want to use Application_Error with my MVC project, but i can't get it to work. I add the following to my Global.asax file:
protected void Application_Error(object sender, EventArgs e)
{
Exception objErr = Server.GetLastError().GetBaseException();
Session["Test"] = "Message:" + objErr.Message.ToString();
}
(The Session is only for tests. Im gonna use a database to log error, if i get this to work.)
Then i try to throw an exception from my HomeController and my Home/Index View, but it only triggers Debug.
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
throw (new Exception());
return View();
}
In my Webconfig file i set a defaulterror page but it doesn't redirect to the view:
<customErrors defaultRedirect="Home/Error">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm" />
</customErrors>
So firstly remember that global error handling should be a last resort, and controller classes have a specific error method for errors;
protected virtual bool OnError(string actionName,
System.Reflection.MethodInfo methodInfo, Exception exception)
Within this you can redirect to the standard shared error view;
protected override bool OnError(string actionName,
System.Reflection.MethodInfo methodInfo, Exception exception)
{
RenderView("Error", exception);
return false;
}
The problem you have in the global application error is that it has no concept of views or controllers, so if you want to redirect in there then you must use a known URL
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
System.Diagnostics.Debug.WriteLine(exception);
Response.Redirect("/Home/Error");
}
but you don't need to do this. If you set the default error page in web.config then you don't need that redirect
<customErrors defaultRedirect="Home/Error" />
However, unless you've added an error view to your Home controller that doesn't exist, so add the following to the home controller
public ActionResult Error()
{
return View();
}
Then (if you're sensible) you'd put the error handling code in the Error() method, as that's where all unhandled errors will end up.
public ActionResult Error()
{
Exception exception = Server.GetLastError();
System.Diagnostics.Debug.WriteLine(exception);
return View();
}
And finally remember that by default you don't see custom errors if you are connecting to localhost! So you need to change that behaviour
<customErrors mode="On" defaultRedirect="/Home/Error" />
Do you have a Session set up in the first place? If the error is triggered from an IHttpHandler not marked with IRequiresSessionState, then accessing Session will fail.
What are you doing with Session["Test"]? Are you sure your code is actually not working? You could try a File.Open and simply output some text (say, the current time) to C:\my-log.txt, which is slightly more likely to succeed than using Session.
GetBaseException isn't useful in this case (nor generally for logging) as far as I can tell.
Message is of type string - calling .ToString() isn't necessary. In general, I'd strongly recommend avoiding ToString() where possible - if you're using it because you're unsure of the type of the object, that should be a red flag; doing an end-run around the type system can hide subtle bugs (for instance, DBNull.Value.ToString() == ""). For GUI's the builtin types provide a .ToString(IFormatProvider) overload which is culture-sensitive and avoid portability issues. Since that overload is also not present on object it's also a safeguard to avoid the very weakly typed .ToString calls.