I have a base controller class where I'm overriding to the Controller.OnException handler method in order to provide a generic error handling for certain types of controllers that will inherit from this class (these are API controllers that will return JSON results). The OnException method never gets called when an exception gets raised by the controller. Does anyone see what I am doing wrong, or is there a better way to handle this situation?
Using MVC 1.0
Base Class:
public class APIController : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
//This is never called
filterContext.Result =
new JsonResult();
base.OnException(filterContext);
}
}
Inheriting Class:
public class MyController : APIController
{
public AjaxResult ForcedException()
{
throw new SystemException();
}
}
If I understand your question correctly - your Exception must be marked as "handled" in your OnException. Try this:
filterContext.ExceptionHandled = true;
The MSDN documentation (http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.onexception.aspx) states that the OnException method is "called when an unhandled exception occurs in the action."
So, it sounds like it might only fire when an unhandled exception happens in an action method.
I created an new controller with one Index action. And the OnException method does in fact execute. I also tried throwing a SystemException() and the OnException method fired as well.
public ActionResult Index ( )
{
throw new NotImplementedException ( );
}
I was having exactly the same problem in my ASP.NET MVC 3 project.
It turns out that this is a feature of Visual Studio and the development server. When in debug mode you will be returned to Visual Studio and given the default exception dialog box.
One way to stop this from happening is to change the compilation mode in your Web.config from this:
<compilation debug="true" targetFramework="4.0">
To this:
<compilation debug="false" targetFramework="4.0">
You can also leave debug mode set to true but test your application is working by choosing the Start without debugging option from the visual studio menu (invoked by pressing Ctrl+F5.
Are you calling the controller action from a unit test, or via ASP.NET? If you're calling the method directly in a test, say through NUnit, then OnException won't fire: the test framework will handle the exception and turn it into a failed test.
The same rules apply for Controller.OnException as for HandleErrorAttribute
The required config for HandleErrorAttribute is noted here https://stackoverflow.com/a/528550/939634
If you have customError in your web.config set to mode="RemoteOnly" and you are debugging locally or you have mode="Off" the exceptions will not be handled and you will see the ASP.Net yellow screen with a stack trace.
If you set mode="On" or you are viewing the site remotely and have mode="RemoteOnly" your OnException method will execute properly.
Apply the [HandleError] attribute to the class.
Related
In short, I've written an error handler controller that has a "HandleError" action that handles all http errors that I care to handle.
For a few reasons (outlined in this answer), I'm catching http errors in two places. One is a global Application_EndRequest method (managed pipeline) and the other is via the <httpErrors> section in the web config (native custom errors module).
If you're wondering why I need the <httpErrors> section, it's because certain responses aren't always caught by the managed pipeline, for example the StaticFile handler catches all urls like ".html,.txt" and will not trigger MVC code.
My controller action looks close to this
public ActionResult HandleError(int statusCode = 0, Exception exception = null)
{
string responseProcessed = Request.Headers.Get("__p__");
if (responseProcessed == null)
{
Request.Headers.Add("__p__", "1");
switch (statusCode)
{
case 401:
return Unauthorized();
case 403:
return Forbidden();
case 404:
return NotFound();
case 500:
return InternalError(exception);
default:
return UnhandledError(exception, statusCode);
}
}
else
{
return null;
}
}
My web.config httpErrors section is currently
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" path="/Errors/HandleError?statusCode=404" responseMode="ExecuteURL"/>
</httpErrors>
Problem
The problem is, the HandleError method is called twice per 404 response because it first hits the custom errors module (specified via the web.config) and then my Application_EndRequest hook catches the 404 which also triggers my action.
Question
The question is, how can I make my HandleError action do nothing to the response instead of replacing it with a blank response (which is what happens when you return null in an action).
Note
Please note that I am aware that I can make my custom 404 error (in web.config) point to my NotFound action explicitly and just return the view each time even though it gets called twice. There is logic (logging, etc) that still needs to only run once and I think it's better design to have it all point to the HandleError method instead to keep repetitive checking logic down and to only runs the action once.
The simple answer is... you cant. All action methods return a type of ActionResult, no matter what. If you do not need an actionresult (or the default EmptyResult that gets sent when you return null) then don't use an action method and consider using a private method instead.
If you do this, you could do as you suggest with a custom 404 and then call this private HandleError method as needed from within your controller.
If this logic needs ran for every request then consider just put it in an action filter instead.
I think you can but you might be over complicating stuff. Lets look at that first :) Does this solve your error handling of static files?
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
You can read more about it here: http://www.hanselman.com/blog/BackToBasicsDynamicImageGenerationASPNETControllersRoutingIHttpHandlersAndRunAllManagedModulesForAllRequests.aspx
I'm trying to understand HandleErrorAttribute in MVC3. (I also followed old article from ScottGu) I added the <customErrors mode="On" /> to the web.config file. All errors redirect to the \Views\Shared\Error.cshtml view. If I keep the HandleErrorAttribute or remove from the controller, there is no difference in the behavior.
Code of the controller
public class HomeController : Controller
{
[HandleError]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
throw new Exception();
return View();
}
}
Also, I show in some articles and SO post, that with <error redirect="..."/>, request can be redirected to the required view.
Qestions
What is the use of HandleErrorAttribute?
What is the advantage of using it over <customErrors.. ?
What can we achieve that is not achievable by <customErrors.. ?
1) The HandleErrorAttribute (MSDN) is a FilterAttribute that is used to handle controller actions that throw an error. I would suggest reading the documentation on the MSDN page as it describes exactly what it does and the constructors that it can take. Additionally in your webconfig you must have the customErrors section set to.
<system.web>
<customErrors mode="On" defaultRedirect="Error" />
</system.web>
2) Now the custom errors section is used to allow the Asp.Net application to control the behavior of the page when an error (Exception) is raised. (MSDN) When the custom Errors is set to On or RemoteOnly when an application exception happens the application will use the rules defined in the Web.config to either display the error message or redirect to a page.
3) Using the HandleErrorAttribute you can provide different redirections \ views based on the exception types raised.
I would recommend you view this SO topic for more information (read Elijah Manor's post). ASP.NET MVC HandleError
Cheers.
There are a lot of similar questions out there but this has me stumped.
If I used [Authorize] I get prompted for a username and password but if I use [InternalAuthorizeV2] I don't
I have a custom AuthorizeAttribute that for the moment does not do any anything special (I'm limiting things that could be wrong).
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class InternalAuthorizeV2Attribute: AuthorizeAttribute
{}
and a Action in my Controller
[InternalAuthorizeV2(Roles = "MobileApps_Parkingggg")]
public ActionResult Index()
{
var model = new VmParking();
return View(model);
}
The login is handled in a different app but they have identical web config lines
<machineKey compatibilityMode="Framework20SP2" validationKey="editedOut" decryptionKey="editedOut" validation="SHA1" decryption="AES"/>
<authentication mode="Forms">
<forms name="emLogin" loginUrl="/Login/Index" timeout="540" protection="All" path="/" enableCrossAppRedirects="true" cookieless="UseCookies" />
</authentication>
<sessionState timeout="540" />
I know that if I login by going to a page with [Authorize] then back to my trouble page I can see the username but it doesn't seem to be calling my customer attribute.
New information:
My attribute is in a shared DLL as it's used by many apps. It seems that if I copy the cs file to the web project it works. No idea why, still looking for hints or tips.
From what you've said, it all behaves fine if you use [Authorize] but not [InternalAuthorizeV2].
Your shared dll shouldn't make any difference if it is set up correctly; I have the same thing working. Make sure the web project is using the latest version of the dll and you have the right assembly references in the shared dll - System.Web.Mvc, v4.0.0.0 in my project.
You say its used by many apps? Do all apps have the same problem with the shared dll or just one of them? If it's just one, check the references for the one with the problem.
If the below tests all work then the final option is that whatever you are doing in your authorize attribute in the dll isn't picking up the right context for that app, or using the right membership provider or database - you haven't included the code you are using inside your attribute so it's hard to know if that could be causing a problem.
Test dependencies
You could try adding a base authorize attribute to your shared dll, and then implementing another authorize attribute in your web project that inherits the base attribute you just created. This should show that you have your shared dll set up correctly.
// in the dll:
public class BaseAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute { ... }
// in the web project:
public class InternalAuthorizeV2Attribute : BaseAuthorizeAttribute { ... }
If simply moving it from your dll project to the web project fixes it, the most likely issue is the web project is not using the right version of the dll (try cleaning and doing a complete rebuild) or your dll is referencing the wrong dlls for the System.Web.Mvc.AuthorizeAttribute. You say you have triple checked, but trying the above debugging should help you work out if this really is the case.
Debug authorization method calls
If that doesn't work then try adding the following override methods to a very simple attribute, and seeing if you hit the breakpoint on the call to base.OnAuthorization. If you don't, then it may not be the actual attributes causing your problem.
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method,
Inherited = true, AllowMultiple = true)]
public class InternalAuthorizeV2Attribute : System.Web.Mvc.AuthorizeAttribute {
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext) {
return false; // breakpoint here, and this should force an authorization failure
}
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext); // breakpoint here
}
}
This should completely prevent any user access to the Action. If that doesn't work then you know the issue doesn't lie in your attribute, but that your attribute is not being applied.
You can also add the following to your controller and check that it is hit before the authorize attribute:
protected override void OnAuthorization(AuthorizationContext filterContext) {
base.OnAuthorization(filterContext);
}
Authorization chaining
Note that you have your attribute attached to the Action method, so it will only be hit if a an authorization attribute earlier in the chain (e.g. a global filter or controller attribute) hasn't already prevented the user being authorized (see my answer here), or prematurely returns an ActionResult that stops the chain reaching your Action attribute. However it's unlikely this is the problem if simply moving it from the dll to the project makes it work. Similarly it's unlikely you have an AllowAnonymous in the wrong place from what you've said.
I have a C# MVC 3 controller with the HandleError attribute at the class level
[HandleError(View = "MyErrorPage")]
public class MyController : Controller
{
At the method level I've got:
[HttpPost]
[MyExceptionHandler]
public ActionResult DoSomeStuff(FormCollection fc)
{
where MyExceptionHandler looks like:
public class MyExceptionHandlerAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
//do Stuff
JsonResult result = new JsonResult {
Data = new {
error = "Your request blah blah...."
}
};
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
}
This works perfectly fine on my localhost. I.e. upon an exception in method 'DoSomeStuff' [MyExceptionHandler] is invoked, the class level [HandleError] is not.
However, on a dev environment, the class level [HandleError] comes into play. The result is that the HandleError view=MyErrorPage is rendered.
I'm aware that [HandleError] is redundant on localhost but open to correction (or configuration options)?
I'm unsure if on the development environment (not localhost) MyExceptionHandler is invoked at all. It may be invoked before the class level [HandleError]?
My problem is that I need [MyExceptionHandler] to be the only handler invoked.
I'd like to be able to have [HandleError] invoked on my localhost so I can simulate the issue locally.
I've found the solution:
[HandleError(View = "MyErrorPage", Order = 2)]
solved the problem (Specifically, Order = 2).
The reason is that this handler now has a lower priority than the method level handler. Thus, the method level handler is executed first, as per the code above , it flags the exception as handled therefore the Controller level handler is never executed.
The reason the problem is only on localhost is because [HandleError] is not enabled on localhost by default. However, on the development environment it was automatically enabled.
I would then think that I'd be able to see the problem on localhost by setting:
<system.web>
<customErrors mode="On" />
but for some reason, that didn't work.... anyway,separate issue I guess.
FYI, I've attempted to summarise all this error handling stuff (with a focus on AJAX) here: http://outbottle.com/net-mvc-3-custom-ajax-error-handling-2/
Thanks
We are creating MVC3 applications. We are using default editors and model state validation. We need to log application errors, but we prefer to make it by some kind of a global handler. We have a handler for unhandled exceptions, but we also want to log model state errors.
The question is: Where can we attach our logger to log such errors? Can we somehow override ModelState or detect situation when model served to view has model errors?
Global filters will most likely be your best way to go.
More from SO here: asp.net mvc 3 handleerror global filter always shows IIS status 500 page
Or checkout the msdn doc here:
http://msdn.microsoft.com/en-us/library/gg416513(v=vs.98).aspx
Create a attribute to handle error and register it in the controller,
public class ErrorHandlerAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext exceptionContext)
{
LogManager.GetLogger("somelogger").Error(exceptionContext.Exception.Message,exceptionContext.Exception);
base.OnException(exceptionContext);
}
}
register it in the controller like this,
[EwmsErrorHandler(ExceptionType = typeof(base type of exception to handle), View = view to redirect)]
public class MyController : Controller
{
<controller code>
}