MVC5 Web API and Dependency Injection

倾然丶 夕夏残阳落幕 提交于 2020-06-12 07:14:48

问题


Trying to do some DI on Web API 2 without third-party tools.
So, from some examples I've got custom dependency resolver (why there's no integrated one? Strange, even Microsoft.Extensions.DependencyInjection provides nothing):

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()
        {

        }
    }

then created this 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;
        }

    }

and registered it:

protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            GlobalConfiguration.Configure(ServiceConfig.Register);
        }

But when I'm trying to use it:

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

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

        public void Get()
        {
            _myService.DoWork();
        }
}

I'm getting error:

An error occurred when trying to create a controller of type 'TestController'. Make sure that the controller has a parameterless public constructor.

How to cook this one in right way?


回答1:


What you see happening is related to this problem. In short, Web API will call its default IHttpControllerActivator implementation to request a new controller instance. That instance will call into your DependencyResolver.GetService method. That method will forward the call to MS.DI's GetService method. However, since you didn't register your controllers into the MS.DI container, it will return null. This will cause the default IHttpControllerActivator to try to create the controller using reflection, but this requires a default constructor. Since the controller doesn't have one, this results in the rather cryptic exception message.

The quick solution, therefore, is to register your controllers, e.g.:

services.AddTransient<TestController>();

This, however, will only partly solve your problem because your IDependencyResolver implementation is broken. It is broken in an ugly way, because it might seem to work at first, but will result in memory leaks, because you always resolve from the root container, instead of resolving from a scope. This will cause your resolved controller instances (and other disposable transient components) to stay referenced for the lifetime of your application.

To fix this, you should change your IDependencyResolver implementation to the following:

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

This implementation will ensure a new IServiceScope is created on each web request and services are always resolved from a request; not from the root IServiceProvider.

Although this will fix your problems, another implementation might still be benificial.

The IDependencyResolver contract is problematic, because it is forced to return null when a call to GetService doesn't result in the correct resolution of a registration. This means that you will end up with these annoying "Make sure that the controller has a parameterless public constructor" errors when you forget to register your controllers.

It is, therefore, much easier to create a custom IHttpControllerActivator instead. In that case you can call GetRequiredService which will 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);
    }
}

This MsDiHttpControllerActivator implementation can be added to the Web API pipeline as follows:

GlobalConfiguration.Configuration.Services
  .Replace(typeof(IHttpControllerActivator),
    new MsDiHttpControllerActivator(services.BuildServiceProvider(true)));

This removes the need to have an IDependencyResolver implementation. You still need to register your controllers, though:

services.AddTransient<TestController>();

Also note that I changed this:

services.BuildServiceProvider()

To this:

services.BuildServiceProvider(true)

This is a really important change; it protects you (for some part) against Captive Dependencies, which are one of the major problems when using DI Containers. For some obscure reason, the BuildServiceProvider() overload defaults to false, which means it will not validate your scopes.



来源:https://stackoverflow.com/questions/55511586/mvc5-web-api-and-dependency-injection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!