How do i use Application_Error in ASP.NET MVC? - asp.net

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.

Related

MVC Exception Filter equivalent in Asp.net web forms

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.

How to re-route exception without marking Exception as handled

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

How to get exception details on shared error.cshtml

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);
}
}

How to obtain a reference to the default ASP.NET page handler or web-services handler?

Consider a Web.config file containing the following httpHandlers declaration:
<httpHandlers>
<add verb="*" path="*" type="MyWebApp.TotalHandlerFactory"/>
</httpHandlers>
In other words, this handler factory wants to “see” all incoming requests so that it gets a chance to handle them. However, it does not necessarily want to actually handle all of them, only those that fulfill a certain run-time condition:
public sealed class TotalHandlerFactory : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
if (some condition is true)
return new MySpecialHttpHandler();
return null;
}
public void ReleaseHandler(IHttpHandler handler) { }
}
However, doing it like this completely overrides the default ASP.NET handler, which means that ASP.NET pages and web services no longer work. I just get a blank page for every URL that doesn’t fulfill the “some condition” in the “if”. Therefore, it seems that returning null is the wrong thing to do.
So what do I need to return instead so that ASP.NET pages and web services are still handled normally?
I would have thought the easiest way would be for your class to inherit from System.Web.UI.PageHandlerFactory and then in an else clause just call base.GetHandler().
public sealed class TotalHandlerFactory : System.Web.UI.PageHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
if (some condition is true)
return new MySpecialHttpHandler();
else
return base.GetHandler(context, requestType, url, pathTranslated)
}
}
I had the same problem, and seems that doing that is not possible using an HttpHandlerFactory.
But, I found a workaround that solved the problem: Using an HttpModule to filter which requests should go to my custom HttpHandler:
First, remove the any reference to your HttpHandler from the web.config.
Then, add a reference to the following HttpModule inside the <Modules> section:
public class MyHttpModule : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication application)
{
application.PostAuthenticateRequest += new EventHandler(application_PostAuthenticateRequest);
}
void application_PostAuthenticateRequest(object sender, EventArgs e)
{
var app = sender as HttpApplication;
var requestUrl = context.Request.Url.AbsolutePath;
if (requestUrl "meets criteria")
{
app.Context.RemapHandler(new MyHttpHandler());
}
}
}
Finally, assume at your HttpHandler that all the incoming request fulfill your criteria, and handle there all the requests.
Without knowing all of your requirements, it sounds like a HttpModule is a more suitable solution for your problem.
It is not possible to do this in the general case.

Session is null in IRouteHandler.GetHttpHandler with Asp.net routing

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.

Resources