Skip to main content
 首页 » 编程设计

c#之MVC5 Web API 和依赖注入(inject)

2024年02月04日33开发

尝试在没有第三方工具的情况下在 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() 
        { 
 
        } 
    } 

然后创建这个类:

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.

如何正确 cooking 这道菜?

请您参考如下方法:

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

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

services.AddTransient<TestController>(); 

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

要解决此问题,您应该将 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 契约(Contract)有问题,因为当调用 GetService 时它被迫返回 null 没有得到正确的解决方案的一个注册。这意味着当您忘记注册您的 Controller 时,您最终会遇到这些烦人的“确保 Controller 具有无参数公共(public)构造函数”错误。

因此,创建自定义 IHttpControllerActivator 会容易得多。在这种情况下,您可以调用 GetRequiredService,它永远不会返回 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 实现的需要。不过,您仍然需要注册您的 Controller :

services.AddTransient<TestController>(); 

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

services.BuildServiceProvider() 

对此:

services.BuildServiceProvider(true) 

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