In my MVC application I am trying to handle errors in my Application_Error method of my HttpApplication. In that handler I do this:
Exception exc = Server.GetLastError();
I'm using Ninject which provides its own DefaultControllerFactory which will throw an exception for non-existent controllers which I can easily catch like this:
if (exc is MyApp.Web.App_Start.ControllerNotFoundException)
{
Response.Clear();
Response.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
Server.ClearError();
log = false;
}
Which works great. I don't want to log these.
The problem is when the controller does exist, but the action does not. For example, I have somebody hitting: admin/config.php. I actually have an AdminController so that doesn't cause a ControllerNotFoundException, it gives me a HttpException with the text:
"A public action method 'config.php' was not found on controller 'MyApp.Web.Controllers.AdminController'."
But I'm other than parsing the text to detect that it's this type of HttpException and not some other, is there a way to tell this is an action not found rather than something else?
I believe this will do what you want. You can inherit the default AsyncControllerActionInvoker class and then inject it.
public class DependencyResolverForControllerActionInvoker : IDependencyResolver
{
private readonly IDependencyResolver innerDependencyResolver;
public DependencyResolverForControllerActionInvoker(IDependencyResolver innerDependencyResolver)
{
if (innerDependencyResolver == null)
throw new ArgumentNullException("innerDependencyResolver");
this.innerDependencyResolver = innerDependencyResolver;
}
public object GetService(Type serviceType)
{
if (typeof(IAsyncActionInvoker).Equals(serviceType) || typeof(IActionInvoker).Equals(serviceType))
{
return new MyAsyncControllerActionInvoker();
}
return this.innerDependencyResolver.GetService(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this.innerDependencyResolver.GetServices(serviceType);
}
}
public class MyAsyncControllerActionInvoker : AsyncControllerActionInvoker
{
public override bool InvokeAction(ControllerContext controllerContext, string actionName)
{
try
{
return base.InvokeAction(controllerContext, actionName);
}
catch (HttpException ex)
{
// Handle unknown action error
}
}
public override bool EndInvokeAction(IAsyncResult asyncResult)
{
try
{
return base.EndInvokeAction(asyncResult);
}
catch (HttpException ex)
{
// Handle unknown action error
}
}
}
Here is a link to the InvokeAction and EndInvokeAction methods so you can try to determine how best to handle any errors it throws.
Usage
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Decorate the current dependency resolver
// (make sure to do this last if using a DI container -
// or alternatively register your type with the DI container)
DependencyResolver.SetResolver(
new DependencyResolverForControllerActionInvoker(DependencyResolver.Current));
}
}
Alternative
You could create a base controller and override the HandleUnknownAction method for a similar (but more tightly coupled) result.
Related
Edit: This question has been modified completely from its original version.
My objective is to commit the database transaction at the action filer level. I am using Web API .Net Framework (4.8) along with Unity DI.
My Transaction Filter Attribute:
public class TransactionFilterAttribute : ActionFilterAttribute
{
private readonly PortalContext _context;
private DbContextTransaction _transactionScope;
public TransactionFilterAttribute(IUnityContainer container)
{
_context = container.Resolve<PortalContext>();
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_transactionScope = _context.Database.BeginTransaction();
using (_transactionScope)
{
base.OnActionExecuting(actionContext);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
try
{
base.OnActionExecuted(actionExecutedContext);
_transactionScope.Commit(); //Error occurred
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
}
Unity Configuration:
public static class UnityConfig
{
private static readonly Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer Container => container.Value;
private static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<PortalContext>();
}
}
I am getting an error while executing _transactionScope.Commit()
Error: Value cannot be null.
Parameter name: connection --> The underlying provider failed on Commit.
Any help would be appreciated.
You could change your code to look something like this. This would open the transaction when the controller gets called and would wait for the method to return before committing the transaction. This is done by the "await next()" part. You should resolve the _databaseContext from your IOC container so make sure to add it at startup. Using a service filter allows you to use the IOC container within a filter. Have a look at this post to learn more about implementing a service filter in .NET projects: https://www.strathweb.com/2015/06/action-filters-service-filters-type-filters-asp-net-5-mvc-6/. The example below it made for an async method, use the sync version of this method if needed.
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
using (var transaction = _champContext.Database.BeginTransaction())
{
var result = await next();
if ((result.Exception == null || result.ExceptionHandled) &&
IsHttpSuccessStatusCode(context.HttpContext.Response.StatusCode))
{
transaction.Commit();
return;
}
transaction.Rollback();
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var controllerName = controllerActionDescriptor?.ControllerName;
var actionName = controllerActionDescriptor?.ActionName;
_logger.Error("Tried to commit transaction for the {ActionName}" +
" method on the {ControllerName} controller with the following parameters: {ActionParameters}" +
" but got exception: {Exception}",
actionName, controllerName, context.ActionArguments, result.Exception);
}
}
This is purely a guess, but I'm wondering if the using clause in your OnActionExecuting override is to blame.
As soon as the base call to OnActionExecuting returns, your _transactionScope is Dispose'd. Thus, assuming that OnActionExecuted isn't called from within the base.OnActionExecuting method, _transactionScope will not be in the state you're expecting in OnActionExecuted.
Suggest:
public override void OnActionExecuting(HttpActionContext actionContext)
{
_transactionScope = _context.Database.BeginTransaction();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
try
{
base.OnActionExecuted(actionExecutedContext);
_transactionScope.Commit(); //Error occurred
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
_transactionScope.Rollback(); // because something went wrong during your transaction
}
finally
{
_transactionScope.Dispose(); // now that we're definitely done.
}
}
I have a simple C# Web Api project that provides a few restful endpoints.
Controller fatal server error handling/logging is generally well described by either using:
Implementing/overriding Application_Error method in Global.asax.cs
protected override void Application_Error(object sender, EventArgs e)
{
var ex = Server.GetLastError();
_logger.Error("Unexpected error while initializing application", ex);
}
Or by adding an exception handler filter:
config.Filters.Add(new ExceptionHandlingAttribute());
OR
GlobalConfiguration.Configuration.Filters.Add(new ExceptionHandlingAttribute());
public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
private static readonly ILog _logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
_logger.Error("Unexpected error in API.", actionExecutedContext.Exception);
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("An error occurred, please try again or contact the administrator."),
ReasonPhrase = "Critical Exception"
});
}
}
However, when an error occurs during instantiation of a controller due to a failure in dependency injection within the constructor of this code:
public class DataController : ApiController
{
private readonly IDataService _dataService;
public DataController(IDataService dataService)
{
_dataService = dataService;
}
[AllowAnonymous]
[HttpGet]
public IHttpActionResult GetSomeStuff()
{
return Ok(new AjaxResponse("somestuff"));
}
none of the above methods catches the error. How can I catch those errors?
This is described very nicely in this blog post. Excerpt to answer question below:
Create a class:
public class GlobalExceptionHandler : ExceptionHandler
{
public async override Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
// Access Exception
// var exception = context.Exception;
const string genericErrorMessage = "An unexpected error occured";
var response = context.Request.CreateResponse(HttpStatusCode.InternalServerError,
new
{
Message = genericErrorMessage
});
response.Headers.Add("X-Error", genericErrorMessage);
context.Result = new ResponseMessageResult(response);
}
}
Then Register you exception handler as below from you application startup or owin setup as below:
public static class SetupFiltersExtensions
{
public static IAppBuilder SetupFilters(this IAppBuilder builder, HttpConfiguration config)
{
config.Services.Replace(typeof (IExceptionHandler), new GlobalExceptionHandler());
return builder;
}
}
As stated in his post, he isn't logging in the above method but prefers to do so through a GlobalErrorLogger as such:
public class GlobalErrorLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
var exception = context.Exception;
// Write your custom logging code here
}
}
Registered as such:
public static class SetupFiltersExtensions
{
public static IAppBuilder SetupFilters(this IAppBuilder builder, HttpConfiguration config)
{
config.Services.Replace(typeof (IExceptionHandler), new GlobalExceptionHandler());
config.Services.Add(typeof(IExceptionLogger), new GlobalErrorLogger());
return builder;
}
}
I finally found the answer myself which is the following.
One has to implement and override the IHttpControllerActivator interface on a GlobalConfiguration.Configuration.Services level (this is important as config.Services only deals with already instantiated controllers).
Here are some snippets:
Startup.cs
// the following will make sure that any errors that happen within the constructor
// of any controller due to dependency injection error will also get logged
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator),
new ExceptionHandlingControllerActivator(
GlobalConfiguration.Configuration.Services.GetHttpControllerActivator())
);
ExceptionHandlingControllerActivator.cs
/// <summary>
/// This class handles instantiation of every api controller. It handles and logs
/// any exception that occurs during instatiation of each controller, e.g. errors
/// that can happen due to dependency injection.
/// </summary>
public class ExceptionHandlingControllerActivator : IHttpControllerActivator
{
private IHttpControllerActivator _concreteActivator;
private static readonly ILog _logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public ExceptionHandlingControllerActivator(IHttpControllerActivator concreteActivator)
{
_concreteActivator = concreteActivator;
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
try
{
return _concreteActivator.Create(request, controllerDescriptor, controllerType);
}
catch (Exception ex)
{
_logger.Error("Internal server error occured while creating API controller " + controllerDescriptor.ControllerName, ex);
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Unexpected error while creating controller " + controllerDescriptor.ControllerName));
}
}
}
I have a global exception filter named LogErrorAttribute:
public class LogErrorAttribute : IExceptionFilter
{
private ILogUtils logUtils;
public void OnException(ExceptionContext filterContext)
{
if (this.logUtils == null)
{
this.logUtils = StructureMapConfig.Container.GetInstance<ILogUtils>();
}
this.logUtils.LogError(HttpContext.Current.User.Identity.GetUserId(), "Unknown error.", filterContext.Exception);
}
}
It's registered along with the standard HandleErrorAttribute filter:
filters.Add(new LogErrorAttribute());
filters.Add(new HandleErrorAttribute());
I'm registering those filters like this:
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
I also have an Application_Error fallback:
protected void Application_Error()
{
var exception = Server.GetLastError();
Server.ClearError();
var httpException = exception as HttpException;
//Logging goes here
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Index";
if (httpException != null)
{
if (httpException.GetHttpCode() == 404)
{
routeData.Values["action"] = "NotFound";
}
Response.StatusCode = httpException.GetHttpCode();
}
else
{
Response.StatusCode = 500;
}
// Avoid IIS7 getting involved
Response.TrySkipIisCustomErrors = true;
// Execute the error controller
if (exception != null)
{
this.errorLogger.Log(LogLevel.Error, "An unknown exception has occurred.", exception);
}
else if (httpException != null)
{
this.errorLogger.Log(LogLevel.Error, "An unknown HTTP exception has occurred.", httpException);
}
else
{
this.errorLogger.Log(LogLevel.Error, "An unknown error has occurred.");
}
}
Now, I have an API controller that grabs some data from the database and then uses AutoMapper to map the models to view models:
var viewModels = AutoMapper.Mapper.Map(users, new List<UserViewModel>());
Inside that AutoMapper configuration a custom resolver executes for one of the properties:
var appModuleAssignments = this.appModuleAssignmentManager.Get(userId);
var appModules = appModuleAssignments.Select(x => this.appModuleManager.Get(x.AppModuleId));
return AutoMapper.Mapper.Map(appModules, new List<AppModuleViewModel>());
At the moment I'm forcing the appModuleManager.Get statement to throw a regular exception:
throw new Exception("Testing global filter.");
This subsequently throws an exception in AutoMapper, both of which are unhandled, however neither the global filter or the Application_Error are picking up this exception.
What did I do wrong here?
A couple things I have done since posting:
Added the customErrors attribute to the Web.config to turn them on.
Removed the HandleErrorAttribute global filter because I realized it was setting the error to handled if it were even running. I would not expect it to be executing anyway because this error occurs outside the controller, but it would have likely bit me later.
The short answer is that you are adding a MVC Exception Filter rather than a Web API Exception Filter.
Your implementation checks for ExceptionContext rather than HttpActionExecutedContext
public override void OnException(HttpActionExecutedContext actionExecutedContext)
Since the framework will raises a Http Exception rather than a MVC Exception, your OnException override method is not triggered.
So, a more complete example:
public class CustomExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
message = "Web API Error";
status = HttpStatusCode.InternalServerError;
actionExecutedContext.Response = new HttpResponseMessage()
{
Content = new StringContent(message, System.Text.Encoding.UTF8, "text/plain"),
StatusCode = status
};
base.OnException(actionExecutedContext);
}
}
Another important step is to register your Global Web API Exception Filter in WebApiConfig.cs, in the Register(HttpConfiguration config) method.
public static void Register(HttpConfiguration config)
{
...
config.Filters.Add(new CustomExceptionFilter());
}
Dave Alperovich answer will solve your issue by using HttpActionExecutedContext
public override void OnException(HttpActionExecutedContext context)
However as you are trying to capture all possible exceptions your application might generate then apart from exception filters one should use message handlers as well. A detailed explanation can be find here - http://www.asp.net/web-api/overview/error-handling/web-api-global-error-handling.
In summary, there are a number of cases that exception filters can’t handle. For example:
Exceptions thrown from controller constructors.
Exceptions thrown from message handlers.
Exceptions thrown during routing.
Exceptions thrown during response content serialization
So, If an unhandled error occurs from anywhere within the application, your Exception Handler will catch it and allow you to take specific action.
//Global exception handler that will be used to catch any error
public class MyExceptionHandler : ExceptionHandler
{
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError,
new ErrorInformation { Message="An unexpected error occured. Please try again later.", ErrorDate=DateTime.UtcNow }));
}
}
I have this custom exception filter:
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
try
{
if (filterContext == null) return;
// Log error details to the DB
}
catch
{
// Intentional empty catch
}
}
}
Which is applied globally in RegisterGlobalFilters (which is called from Application_Start):
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomExceptionFilter());
filters.Add(new HandleErrorAttribute());
}
I then have a protected constructor on my BaseController which calls this method:
public Site GetSiteByUrl(string host)
{
var urls = _repo.Urls.Where(x => x.Host == host);
if (urls == null || !urls.Any())
throw new MultiTenancyException(String.Format("No URL record exists for request. Host = \"{0}\"", host));
if (urls.Count() > 1)
throw new MultiTenancyException(String.Format("Multiple URL records exist for request. Host = \"{0}\"",
host));
var url = urls.Single();
var site = _repo.Sites.Single(x => x.Id == url.SiteId);
if (!url.Enabled)
throw new MultiTenancyException(
String.Format("URL record found for request, but is not Enabled. Host = \"{0}\", Site = \"{1}\"",
host, site.Name));
return site;
}
When any of the MultiTenancyExceptions in this method are thrown the OnException event of my CustomExceptionFilter is not triggered.
I've tried:
Using a basic Exception rather than my custom MultiTenancyException.
Applying the CustomExceptionFilter as an attribute on the controller rather than globally via RegisterGlobalFilters.
Both to no avail. Looking back through the logs of exceptions that have been caught by the CustomExceptionFilter the only logged errors seem to by system exceptions (NullReference, ArgumentOutOfRange etc.) which may be relevant or may simply be a coincidence.
I've been Googling around for about 30 minutes and am starting to bang my head against the wall at this point, so I'm looking for any sensible ideas.
Thanks.
Filters from GlobalFilterCollection are applied only for exceptions occurred while controller action execution.
To catch exceptions occurred before or later action execution you should define Application_Error method in your Global.asax file:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
void Application_Error(object sender, EventArgs e)
{
// handle global errors here
}
}
In mvc asp.net, I can override a factory to create my controllers, and so put a reference to my IOC just here. Doing So every interface needed by the constructor of my controllers will be feeded by my IOC .
Is there some common way to do it using Silverlight?
At the moment I only found to use the kernel of Ninject everywhere :
public partial class MyUserControlSL
{
public MyUserControlSL()
{
DataContext = new MyViewModel(Kernel.Get<IMyRepository>());
InitializeComponent();
}
}
eg using StructureMap and MVC:
public class ControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType)
{
IController result = null;
try
{
if (controllerType != null)
{
result = ObjectFactory.GetInstance(controllerType)
as Controller;
}
else
{
return base.GetControllerInstance(
requestContext, controllerType);
}
}
catch (StructureMapException)
{
System.Diagnostics.Debug.WriteLine(
ObjectFactory.WhatDoIHave());
throw;
}
return result;
}
}
public AController(IServiceA serviceA)
{
if (serviceA == null)
{
throw new Exception("IServiceA cannot be null");
}
_ServiceA = serviceA;
}
public ServiceA(IRepositoryA repository)
{
if (repository == null)
{
throw new Exception(
"the repository IRepositoryA cannot be null");
}
_Repository = repository;
}
Thanks for your help, please ask if it is not clear..
In Silverlight you should use a bootstrapper at composition root to wire up your entire object graph. It could be the Application class app.xml.cs and look similar to
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
}
In general this should be sufficant but if you need a separate Factory class for your views, take a look at
Keeping the DI-container usage in the composition root in Silverlight and MVVM.
For Silverlight you may use PRISM framework with custom IoC container.
Autofac has built in support for silverlight: http://weblogs.asp.net/dwahlin/archive/2010/01/03/using-autofac-as-an-ioc-container-in-silverlight-applications.aspx