1

I have a controller in Web Api 2 and use Unity to inject dependencies into it. This seemed to work just fine until I suddenly got these exceptions in my log. The first two lines are from successful invocations of the controller (hundres of invocations prior worked just fine) and the remaining are from a failed invocations (there are hundreds of failed exceptions). What could trigger these exceptions during runtime when during the same run the controller also worked just fine? The application pool was recycled automatically (seen by the low ms number in the log) and the controller again worked fine (I guess until it again turns bad).

DEBUG 2016-04-07 04:33:56,611 50302443ms EventController        Post               [eaaa3010-36ec-4cba-b9c3-22035c62430a] - Received: ...
DEBUG 2016-04-07 04:33:56,611 50302443ms EventController        Post               [eaaa3010-36ec-4cba-b9c3-22035c62430a] - Parsed ...
ERROR 2016-04-07 06:08:58,424 56004256ms LogExceptionLogger     LogAsync           [(null)] - Global exception:
System.InvalidOperationException: An error occurred when trying to create a controller of type 'EventController'. Make sure that the controller has a parameterless public constructor. ---> System.ArgumentException: Type 'KamstrupEventReceiver.Controllers.EventController' does not have a default constructor
   at System.Linq.Expressions.Expression.New(Type type)
   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)
   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
   --- End of inner exception stack trace ---
   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
   at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
...
ERROR 2016-04-07 08:46:36,315 65462147ms LogExceptionLogger     LogAsync           [(null)] - Global exception:
System.InvalidOperationException: An error occurred when trying to create a controller of type 'EventController'. Make sure that the controller has a parameterless public constructor. ---> System.ArgumentException: Type 'KamstrupEventReceiver.Controllers.EventController' does not have a default constructor
   at System.Linq.Expressions.Expression.New(Type type)
   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)
   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
   --- End of inner exception stack trace ---
   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
   at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
DEBUG 2016-04-07 14:33:23,606 51408ms EventController        Post               [b88bada7-d81d-4bef-84d8-ce884cc3a010] - Received: ...
DEBUG 2016-04-07 14:33:23,887 51689ms EventController        Post               [b88bada7-d81d-4bef-84d8-ce884cc3a010] - Parsed ...

My controller is this:

[LogExceptionFilter]
public class EventController : ApiController
{
    private static readonly ILog log4 = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    private IEventMapping _eventMapping;
    private IMessageQueue _messageQueue;
    private IMapper _mapper;

    public EventController(IEventMapping eventMapping, IMessageQueue messageQueue, IMapper mapper)
    {
        _eventMapping = eventMapping;
        _messageQueue = messageQueue;
        _mapper = mapper;
    }

    // POST: api/Event
    [HttpPost]
    public IHttpActionResult Post([FromBody]Event @event) { ... }

and Unity is registered like this in WebApiConfig.cs:

        var container = new UnityContainer();
        container.RegisterType<IEventMapping, EventMapping>(new HierarchicalLifetimeManager());
        container.RegisterType<IMessageQueue, MessageQueue>(new HierarchicalLifetimeManager());
        container.RegisterInstance<IMapper>(new MapperConfiguration(cfg => {
            cfg.CreateMap<Event, EventDTO>();
        }).CreateMapper());
        config.DependencyResolver = new UnityResolver(container);

        config.Filters.Add(new LogExceptionFilterAttribute());
        config.Services.Add(typeof(IExceptionLogger), new LogExceptionLogger());

This is the UnityResolver:

public class UnityResolver : IDependencyResolver
{
    protected IUnityContainer container;

    public UnityResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return new List<object>();
        }
    }

    public IDependencyScope BeginScope()
    {
        var child = container.CreateChildContainer();
        return new UnityResolver(child);
    }

    public void Dispose()
    {
        container.Dispose();
    }
}
galmok
  • 869
  • 10
  • 21

1 Answers1

2

The real exception is swept away and and is impossible for you to see the real cause. The reason this is happening is because:

  • of the design of Web API, forcing IDependencyResolver.GetService to return null in case of an error.
  • the way you implemented your UnityResolver.

This answer describes how the resolve pipeline of Web API works and why you get this stupid "Make sure that the controller has a parameterless public constructor" exception message. In the case of the linked question, the OP didn't register his controllers explicitly. In your case the problem is different though, because only in some circumstances your controllers aren't resolved. There are a few reasons I can think of why this might happen:

  1. You are doing too much work in your injection constructors; constructors should be simple. This causes them to fail because some external system (database, web service, etc) is unavailable.
  2. There is a concurrency bug in your code, or in Unity.

Since you're IDependencyResolver.GetService method catches all ResolutionFailedExceptions and returns null, you lose important exception information. The best solution is to actually refrain from using the IDependencyResolver abstraction at all, or at least, ensure that controllers are resolved using the proper abstraction: The IHttpControllerActivator. Here's an example:

public sealed class UnityControllerActivator : IHttpControllerActivator
{
    private readonly UnityContainer container;
    public UnityControllerActivator(UnityContainer container) {
        this.container = container;
    }
    public IHttpController Create(HttpRequestMessage request, 
        HttpControllerDescriptor controllerDescriptor, Type controllerType) {
        // Make sure there is an active scope before calling resolve.
        request.GetDependencyScope();
        return (IHttpController)this.container.Resolve(controllerType);
    }
}

You can register this UnityControllerActivator in Unity as follows:

configuration.Services.Replace(typeof(IHttpControllerActivator), 
    new UnityControllerActivator());

What this ensures is that Web API will use this custom UnityControllerActivator instead of using its DefaultHttpControllerActivator. The DefaultHttpControllerActivator calls back into the IDependencyResolver, but it can return null. Instead, our custom UnityControllerActivator will call directly into Unity and will never return null. Instead, the original exception thrown by Unity is preserved and logged. This allows you to analyze the real cause. If this new exception makes no sense to you, you might want to come back here and ask a new question on Stackoverflow.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Thanks. My own constructors are simple. But the calls into the controller are slow (seconds apart) so the risk for overlapping requests are very slim. But I don't believe I have any concurrency issue in my code. Should I still use `config.DependencyResolver = new UnityResolver(container);`? – galmok Apr 08 '16 at 10:32
  • @galmok: Whether to replace the default `DependencyResolver` depends on whether you are using Unity to resolve other services that Web API requests. In general I would say that you only want to intercept the creation of controllers, so you don't need the `UnityResolver` anymore. – Steven Apr 08 '16 at 10:46
  • There is an issue with the proposed solution. When I register types with HierarchicalLifetimeManager, the registered object is still reused for each invocation of the controller instantiation. Without the proposed solution, lifetime is the same as controller method invocation. How would I modify your solution to accomodate this? – galmok Apr 08 '16 at 12:53
  • @galmok: I'm sorry, I forgot about that. You need the `UnityResolver` for the scoping that it provides. You can't do without it, but by adding the `UnityControllerActivator` you will not be left in the dark about any resolution exception that might occur. – Steven Apr 08 '16 at 12:57
  • I already have both `UnityResolver` and `UnityControllerActivator` active, but the hierarchical scope isn't respected for the resolved objects. Wouldn't I get the correct exception if I added logging to the `UnityResolver.GetService()` method? – galmok Apr 08 '16 at 13:06
  • @galmok: See my update to the `UnityControllerActivator`. I forgot the `GetDependencyScope()` call. Don't you just love Web API ;-). You can add logging, but this adds a lot of noise, because Web API calls `GetService` quite a lot for services that you didn't register in Unity. This will pollute your log file with false positives. – Steven Apr 08 '16 at 13:12
  • Thank you for your help. The correction you made didn't make a difference, though. The requested lifetime is still not respected. – galmok Apr 08 '16 at 14:18
  • @galmok In that case I am lost. I advise you to add the logging to the UnityResolver. – Steven Apr 08 '16 at 14:22