Unity IoC在运行时突然失败

时间:2016-04-07 14:18:13

标签: c# exception runtime inversion-of-control

我在Web Api 2中有一个控制器,并使用Unity将依赖项注入其中。这似乎工作得很好,直到我突然在我的日志中遇到这些异常。前两行是来自控制器的成功调用(之前工作正常的调用),其余来自失败的调用(有数百个失败的异常)。什么可以在运行期间触发这些异常,在同一运行期间控制器也工作得很好?应用程序池自动回收(通过日志中的低ms编号看到),控制器再次工作正常(我想直到它再次变坏)。

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 ...

我的控制器是:

[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) { ... }

和Unity在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());

这是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();
    }
}

1 个答案:

答案 0 :(得分:2)

真正的异常被扫除了,你无法看到真正的原因。发生这种情况的原因是:

  • Web API的设计,强制IDependencyResolver.GetService在发生错误时返回null。
  • 您实施UnityResolver
  • 的方式

This answer描述了Web API的解析流程如何工作以及为什么会出现这种愚蠢的“确保控制器具有无参数的公共构造函数”异常消息。在链接问题的情况下,OP没有明确地注册他的控制器。在您的情况下,问题是不同的,因为只有在某些情况下您的控制器不会被解析。我可以想到为什么会发生这种情况有几个原因:

  1. 你在注射构造中做了太多工作; constructors should be simple。这会导致它们失败,因为某些外部系统(数据库,Web服务等)不可用。
  2. 您的代码或Unity中存在并发错误。
  3. 由于您IDependencyResolver.GetService方法捕获所有ResolutionFailedException并返回null,因此会丢失重要的异常信息。最好的解决方案是实际上完全不使用IDependencyResolver抽象,或者至少确保使用适当的抽象来解析控制器:IHttpControllerActivator。这是一个例子:

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

    您可以在Unity中注册此UnityControllerActivator,如下所示:

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

    这可以确保Web API将使用此自定义UnityControllerActivator而不是使用其DefaultHttpControllerActivatorDefaultHttpControllerActivator会回复IDependencyResolver,但可以返回null。相反,我们的自定义UnityControllerActivator会直接调用Unity,永远不会返回null。而是保留并记录Unity抛出的原始异常。这允许您分析真正的原因。如果这个新异常对你没有意义,你可能想回到这里并在Stackoverflow上提出一个新问题。