Unity IoC 在运行时突然失败
Unity IoC suddenly fails during runtime
我在 Web 中有一个控制器 Api 2 并使用 Unity 将依赖项注入其中。这似乎工作得很好,直到我突然在我的日志中出现这些异常。前两行来自控制器的成功调用(之前的数百次调用工作得很好),其余的来自失败的调用(有数百次失败的异常)。什么可以在 运行 时间内触发这些异常,而在同一 运行 控制器也能正常工作?应用程序池自动回收(从日志中的低毫秒数可以看出)并且控制器再次工作正常(我猜直到它再次变坏)。
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();
}
}
真正的异常一扫而光,你不可能看到真正的原因。发生这种情况的原因是:
- Web API 的设计,强制
IDependencyResolver.GetService
为 return null,以防出现错误。
- 您实施
UnityResolver
的方式。
This answer 描述了 Web API 的解析管道是如何工作的,以及为什么你会收到这个愚蠢的 "Make sure that the controller has a parameterless public constructor" 异常消息。在链接问题的情况下,OP 没有明确注册他的控制器。但在您的情况下,问题有所不同,因为只有在某些情况下您的控制器才没有得到解决。我能想到为什么会发生这种情况有几个原因:
- 您在注入构造函数中做了太多工作; constructors should be simple。这会导致它们失败,因为某些外部系统(数据库、Web 服务等)不可用。
- 您的代码或 Unity 中存在并发错误。
由于您的 IDependencyResolver.GetService
方法捕获了所有 ResolutionFailedException
和 return 的空值,因此您丢失了重要的异常信息。最好的解决方案是完全避免使用 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
而不是使用其 DefaultHttpControllerActivator
。 DefaultHttpControllerActivator
回调到 IDependencyResolver
,但它可以 return null
。相反,我们的自定义 UnityControllerActivator
将直接调用 Unity,永远不会 return null
。相反,Unity 抛出的原始异常会被保留并记录下来。这使您可以分析真正的原因。如果这个新异常对您来说没有意义,您可能想回到这里并在 Whosebug 上提出一个新问题。
我在 Web 中有一个控制器 Api 2 并使用 Unity 将依赖项注入其中。这似乎工作得很好,直到我突然在我的日志中出现这些异常。前两行来自控制器的成功调用(之前的数百次调用工作得很好),其余的来自失败的调用(有数百次失败的异常)。什么可以在 运行 时间内触发这些异常,而在同一 运行 控制器也能正常工作?应用程序池自动回收(从日志中的低毫秒数可以看出)并且控制器再次工作正常(我猜直到它再次变坏)。
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();
}
}
真正的异常一扫而光,你不可能看到真正的原因。发生这种情况的原因是:
- Web API 的设计,强制
IDependencyResolver.GetService
为 return null,以防出现错误。 - 您实施
UnityResolver
的方式。
This answer 描述了 Web API 的解析管道是如何工作的,以及为什么你会收到这个愚蠢的 "Make sure that the controller has a parameterless public constructor" 异常消息。在链接问题的情况下,OP 没有明确注册他的控制器。但在您的情况下,问题有所不同,因为只有在某些情况下您的控制器才没有得到解决。我能想到为什么会发生这种情况有几个原因:
- 您在注入构造函数中做了太多工作; constructors should be simple。这会导致它们失败,因为某些外部系统(数据库、Web 服务等)不可用。
- 您的代码或 Unity 中存在并发错误。
由于您的 IDependencyResolver.GetService
方法捕获了所有 ResolutionFailedException
和 return 的空值,因此您丢失了重要的异常信息。最好的解决方案是完全避免使用 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
而不是使用其 DefaultHttpControllerActivator
。 DefaultHttpControllerActivator
回调到 IDependencyResolver
,但它可以 return null
。相反,我们的自定义 UnityControllerActivator
将直接调用 Unity,永远不会 return null
。相反,Unity 抛出的原始异常会被保留并记录下来。这使您可以分析真正的原因。如果这个新异常对您来说没有意义,您可能想回到这里并在 Whosebug 上提出一个新问题。