MVC5 Web API 和依赖注入
MVC5 Web API and Dependency Injection
尝试在 Web 上做一些 DI API 2 没有第三方工具。
所以,从一些例子中我得到了自定义依赖解析器(为什么没有集成的解析器?奇怪,甚至 Microsoft.Extensions.DependencyInjection 什么也没有提供):
public class DependencyResolver : IDependencyResolver
{
protected IServiceProvider _serviceProvider;
public DependencyResolver(IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider;
}
public IDependencyScope BeginScope()
{
return this;
}
public void Dispose()
{
}
public object GetService(Type serviceType)
{
return this._serviceProvider.GetService(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this._serviceProvider.GetServices(serviceType);
}
public void AddService()
{
}
}
然后创建了这个 class:
public class ServiceConfig
{
public static void Register(HttpConfiguration config)
{
var services = new ServiceCollection();
services.AddScoped<IMyService, MyServiceClient>();
var resolver = new DependencyResolver(services.BuildServiceProvider());
config.DependencyResolver = resolver;
}
}
并注册:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
GlobalConfiguration.Configure(ServiceConfig.Register);
}
但是当我尝试使用它时:
public class TestController : ApiController
{
private IMyService _myService = null;
public TestController(IMyService myService)
{
_myService = myService;
}
public void Get()
{
_myService.DoWork();
}
}
我遇到错误:
An error occurred when trying to create a controller of type 'TestController'. Make sure that the controller has a parameterless public constructor.
如何正确烹饪这道菜?
您看到的情况与 this problem 有关。简而言之,Web API 将调用其默认的 IHttpControllerActivator
实现来请求新的控制器实例。该实例将调用您的 DependencyResolver.GetService
方法。该方法会将调用转发给 MS.DI 的 GetService
方法。但是,由于 您没有将控制器注册到 MS.DI 容器 ,它将 return null
。这将导致默认 IHttpControllerActivator
尝试使用反射创建控制器,但这需要默认构造函数。由于控制器没有控制器,因此会产生相当神秘的异常消息。
因此,快速的解决方案是注册您的控制器,例如:
services.AddTransient<TestController>();
但是,这只能部分解决您的问题,因为 您的 IDependencyResolver
实施已损坏 。它以一种丑陋的方式被破坏,因为它一开始似乎可以工作,但会导致内存泄漏,因为你总是从根容器解析,而不是从范围解析。这将导致您解析的控制器实例(和其他一次性瞬态组件)在您的应用程序的生命周期内保持引用。
要解决此问题,您应该将 IDependencyResolver
实施更改为以下内容:
public class DependencyResolver : IDependencyResolver
{
private readonly IServiceProvider provider;
private readonly IServiceScope scope;
public DependencyResolver(ServiceProvider provider) => this.provider = provider;
internal DependencyResolver(IServiceScope scope)
{
this.provider = scope.ServiceProvider;
this.scope = scope;
}
public IDependencyScope BeginScope() =>
new DependencyResolver(provider.CreateScope());
public object GetService(Type serviceType) => provider.GetService(serviceType);
public IEnumerable<object> GetServices(Type type) => provider.GetServices(type);
public void Dispose() => scope?.Dispose();
}
此实现将确保在每个 Web 请求上创建一个新的 IServiceScope
,并且服务始终从请求中解析;不是来自根 IServiceProvider
.
尽管这会解决您的问题,但其他实施方式可能仍然有益。
IDependencyResolver
合同有问题,因为当对 GetService
的调用未导致正确的注册解析时,它被迫 return null
.这意味着当您忘记注册您的控制器时,您将遇到这些恼人的 "Make sure that the controller has a parameterless public constructor" 错误。
因此,创建自定义 IHttpControllerActivator
更容易。在这种情况下,您可以调用 GetRequiredService
,它将 never return null
:
public class MsDiHttpControllerActivator : IHttpControllerActivator
{
private readonly ServiceProvider provider;
public MsDiHttpControllerActivator(ServiceProvider provider) =>
this.provider = provider;
public IHttpController Create(
HttpRequestMessage request, HttpControllerDescriptor d, Type controllerType)
{
IServiceScope scope = this.provider.CreateScope();
request.RegisterForDispose(scope); // disposes scope when request ends
return (IHttpController)scope.ServiceProvider.GetRequiredService(controllerType);
}
}
此 MsDiHttpControllerActivator
实现可以添加到 Web API 管道,如下所示:
GlobalConfiguration.Configuration.Services
.Replace(typeof(IHttpControllerActivator),
new MsDiHttpControllerActivator(services.BuildServiceProvider(true)));
这消除了 IDependencyResolver
实施的需要。不过,您仍然需要注册您的控制器:
services.AddTransient<TestController>();
另请注意,我对此进行了更改:
services.BuildServiceProvider()
为此:
services.BuildServiceProvider(true)
这是一个非常重要的变化;它保护你(在某种程度上)免受 Captive Dependencies 的影响,这是使用 DI 容器时的主要问题之一。由于某些不明确的原因,BuildServiceProvider()
重载默认为 false
,这意味着它不会验证您的范围。
尝试在 Web 上做一些 DI API 2 没有第三方工具。
所以,从一些例子中我得到了自定义依赖解析器(为什么没有集成的解析器?奇怪,甚至 Microsoft.Extensions.DependencyInjection 什么也没有提供):
public class DependencyResolver : IDependencyResolver
{
protected IServiceProvider _serviceProvider;
public DependencyResolver(IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider;
}
public IDependencyScope BeginScope()
{
return this;
}
public void Dispose()
{
}
public object GetService(Type serviceType)
{
return this._serviceProvider.GetService(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this._serviceProvider.GetServices(serviceType);
}
public void AddService()
{
}
}
然后创建了这个 class:
public class ServiceConfig
{
public static void Register(HttpConfiguration config)
{
var services = new ServiceCollection();
services.AddScoped<IMyService, MyServiceClient>();
var resolver = new DependencyResolver(services.BuildServiceProvider());
config.DependencyResolver = resolver;
}
}
并注册:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
GlobalConfiguration.Configure(ServiceConfig.Register);
}
但是当我尝试使用它时:
public class TestController : ApiController
{
private IMyService _myService = null;
public TestController(IMyService myService)
{
_myService = myService;
}
public void Get()
{
_myService.DoWork();
}
}
我遇到错误:
An error occurred when trying to create a controller of type 'TestController'. Make sure that the controller has a parameterless public constructor.
如何正确烹饪这道菜?
您看到的情况与 this problem 有关。简而言之,Web API 将调用其默认的 IHttpControllerActivator
实现来请求新的控制器实例。该实例将调用您的 DependencyResolver.GetService
方法。该方法会将调用转发给 MS.DI 的 GetService
方法。但是,由于 您没有将控制器注册到 MS.DI 容器 ,它将 return null
。这将导致默认 IHttpControllerActivator
尝试使用反射创建控制器,但这需要默认构造函数。由于控制器没有控制器,因此会产生相当神秘的异常消息。
因此,快速的解决方案是注册您的控制器,例如:
services.AddTransient<TestController>();
但是,这只能部分解决您的问题,因为 您的 IDependencyResolver
实施已损坏 。它以一种丑陋的方式被破坏,因为它一开始似乎可以工作,但会导致内存泄漏,因为你总是从根容器解析,而不是从范围解析。这将导致您解析的控制器实例(和其他一次性瞬态组件)在您的应用程序的生命周期内保持引用。
要解决此问题,您应该将 IDependencyResolver
实施更改为以下内容:
public class DependencyResolver : IDependencyResolver
{
private readonly IServiceProvider provider;
private readonly IServiceScope scope;
public DependencyResolver(ServiceProvider provider) => this.provider = provider;
internal DependencyResolver(IServiceScope scope)
{
this.provider = scope.ServiceProvider;
this.scope = scope;
}
public IDependencyScope BeginScope() =>
new DependencyResolver(provider.CreateScope());
public object GetService(Type serviceType) => provider.GetService(serviceType);
public IEnumerable<object> GetServices(Type type) => provider.GetServices(type);
public void Dispose() => scope?.Dispose();
}
此实现将确保在每个 Web 请求上创建一个新的 IServiceScope
,并且服务始终从请求中解析;不是来自根 IServiceProvider
.
尽管这会解决您的问题,但其他实施方式可能仍然有益。
IDependencyResolver
合同有问题,因为当对 GetService
的调用未导致正确的注册解析时,它被迫 return null
.这意味着当您忘记注册您的控制器时,您将遇到这些恼人的 "Make sure that the controller has a parameterless public constructor" 错误。
因此,创建自定义 IHttpControllerActivator
更容易。在这种情况下,您可以调用 GetRequiredService
,它将 never return null
:
public class MsDiHttpControllerActivator : IHttpControllerActivator
{
private readonly ServiceProvider provider;
public MsDiHttpControllerActivator(ServiceProvider provider) =>
this.provider = provider;
public IHttpController Create(
HttpRequestMessage request, HttpControllerDescriptor d, Type controllerType)
{
IServiceScope scope = this.provider.CreateScope();
request.RegisterForDispose(scope); // disposes scope when request ends
return (IHttpController)scope.ServiceProvider.GetRequiredService(controllerType);
}
}
此 MsDiHttpControllerActivator
实现可以添加到 Web API 管道,如下所示:
GlobalConfiguration.Configuration.Services
.Replace(typeof(IHttpControllerActivator),
new MsDiHttpControllerActivator(services.BuildServiceProvider(true)));
这消除了 IDependencyResolver
实施的需要。不过,您仍然需要注册您的控制器:
services.AddTransient<TestController>();
另请注意,我对此进行了更改:
services.BuildServiceProvider()
为此:
services.BuildServiceProvider(true)
这是一个非常重要的变化;它保护你(在某种程度上)免受 Captive Dependencies 的影响,这是使用 DI 容器时的主要问题之一。由于某些不明确的原因,BuildServiceProvider()
重载默认为 false
,这意味着它不会验证您的范围。