MVC5 Web API 和依赖注入

Tro*_*acy 3 c# dependency-injection asp.net-web-api asp.net-mvc-5 asp.net-web-api2

尝试在没有第三方工具的情况下在 Web API 2 上做一些 DI。
因此,从一些示例中我得到了自定义依赖项解析器(为什么没有集成的?奇怪,甚至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()
        {

        }
    }
Run Code Online (Sandbox Code Playgroud)

然后创建了这个类:

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

    }
Run Code Online (Sandbox Code Playgroud)

并注册它:

protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            GlobalConfiguration.Configure(ServiceConfig.Register);
        }
Run Code Online (Sandbox Code Playgroud)

但是当我尝试使用它时:

public class TestController : ApiController
    {
        private IMyService _myService = null;

        public TestController(IMyService myService)
        {
            _myService = myService;
        }

        public void Get()
        {
            _myService.DoWork();
        }
}
Run Code Online (Sandbox Code Playgroud)

我收到错误:

尝试创建类型为“TestController”的控制器时出错。确保控制器具有无参数的公共构造函数。

如何以正确的方式烹饪这个?

Ste*_*ven 7

您看到的情况与此问题有关。简而言之,Web API 将调用其默认IHttpControllerActivator实现来请求新的控制器实例。该实例将调用您的DependencyResolver.GetService方法。该方法将调用转发到 MS.DI 的GetService方法。但是,由于您没有将控制器注册到 MS.DI 容器中,因此它将返回null. 这将导致默认IHttpControllerActivator尝试使用反射创建控制器,但这需要默认构造函数。由于控制器没有,这会导致相当神秘的异常消息。

因此,快速的解决方案是注册您的控制器,例如:

services.AddTransient<TestController>();
Run Code Online (Sandbox Code Playgroud)

但是,这只能部分解决您的问题,因为您的IDependencyResolver实现已损坏。它以一种丑陋的方式被破坏,因为它起初似乎可以工作,但会导致内存泄漏,因为您总是从根容器解析,而不是从作用域解析。这将导致您解析的控制器实例(和其他一次性瞬态组件)在您的应用程序的生命周期内保持引用。

要解决此问题,您应该将IDependencyResolver实现更改为以下内容:

services.AddTransient<TestController>();
Run Code Online (Sandbox Code Playgroud)

这个实现将确保IServiceScope在每个 Web 请求上创建一个新的,并且服务总是从一个请求中得到解决;不是从根本上IServiceProvider

尽管这将解决您的问题,但另一个实现可能仍然是有益的。

IDependencyResolver合同是有问题的,因为它是被迫返回null时,一个电话GetService在注册的正确的分辨率不会导致。这意味着当您忘记注册控制器时,您最终会遇到这些烦人的“确保控制器具有无参数的公共构造函数”错误。

因此,创建自定义要容易得多IHttpControllerActivator。在这种情况下,您可以调用GetRequiredServicewhich永远不会返回null

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();
}
Run Code Online (Sandbox Code Playgroud)

MsDiHttpControllerActivator实现可以添加到 Web API 管道中,如下所示:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这消除了对IDependencyResolver实现的需要。不过,您仍然需要注册您的控制器:

services.AddTransient<TestController>();
Run Code Online (Sandbox Code Playgroud)

另请注意,我对此进行了更改:

services.BuildServiceProvider()
Run Code Online (Sandbox Code Playgroud)

对此:

services.BuildServiceProvider(true)
Run Code Online (Sandbox Code Playgroud)

这是一个非常重要的变化;它可以保护您(在某些方面)免受Captive Dependencies 的影响,这是使用 DI Containers 时的主要问题之一。由于某些晦涩的原因,BuildServiceProvider()重载默认为false,这意味着它不会验证您的范围。