简体   繁体   中英

Use custom IServiceProvider implementation in asp.net core

I have implemented an adapter that implement IServiceProvider and returned it from the ConfigureServices method in the Startup. class:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    var kernel = new StandardKernel();

    var container = new NinjectComponentContainer(kernel);

    // ...

    return ServiceProviderFactory.Create(container, services);
}

However, my implementation doesn't seem to be used everywhere. I even tried to override the IHttpContextAccessor to return a modified HttpContext :

    public HttpContext HttpContext {
        get
        {
            var result = _httpContextAccessor.HttpContext;

            result.RequestServices = _serviceProvider;

            return result;
        }
        set => _httpContextAccessor.HttpContext = value;
    }

To test whether I could get to my implementation I used a filter in order to see what the HttpContext.RequestServices would return:

public class AuthorizationTestAttribute : ActionFilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var service = context.HttpContext.RequestServices.GetService(typeof(IAccessConfiguration));
    }
}

The type returned by context.HttpContext.RequestServices is:

在此处输入图片说明

My main issue was trying to get registered components resolved in the constructor of a filter but it always seems to fail saying the component is not registered. However it does seem to work when using the TypeFilter attribute:

[TypeFilter(typeof(RequiresSessionAttribute))]

However, my attribute does inherit from TypeFilter :

public class RequiresSessionAttribute : TypeFilterAttribute
{
    public RequiresSessionAttribute() : base(typeof(RequiresSession))
    {
        Arguments = new object[] { };
    }

    private class RequiresSession : IAuthorizationFilter
    {
        private readonly IAccessConfiguration _configuration;
        private readonly IDatabaseContextFactory _databaseContextFactory;
        private readonly ISessionQuery _sessionQuery;

        public RequiresSession(IAccessConfiguration configuration,
            IDatabaseContextFactory databaseContextFactory, ISessionQuery sessionQuery)
        {
            Guard.AgainstNull(configuration, nameof(configuration));
            Guard.AgainstNull(databaseContextFactory, nameof(databaseContextFactory));
            Guard.AgainstNull(sessionQuery, nameof(sessionQuery));

            _configuration = configuration;
            _databaseContextFactory = databaseContextFactory;
            _sessionQuery = sessionQuery;
        }

I did come across this question but there is no definitive answer.

Any ideas on how to correctly provider a custom implementation of the IServiceProvider interface that will be used throughout the solution?

Even though Microsoft states that it is possible to replace the built-in container it appears as though it is not quite this simple, or even possible.

As stated by Steven in his very first comment, if you choose to use your container of choice, you should run them side-by-side.

The guidance from Microsoft suggests changing the ConfigureServices in the Startup class from this:

public void ConfigureServices(IServiceCollection services)
{
    // registrations into services
}

to the following:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    var container = new YourContainer(); // Castle, Ninject, etc.

    // registrations into container

    return new YourContainerAdapter(container);
}

However, there are a number of issues with this since there are already framework registrations in services that we do not necessarily know how to re-register in our own container. Well, there is a descriptor so if our container supports all the various methods then it is actually possible to re-register all the components. The various DI containers have different mechanisms when it comes to registration and service resolution. Some of them have a very hard distinction between the two making it quite tricky at times to accommodate a "common" solution.

My initial idea was to provide an adapter that accepts both my own container as well as the services collection from which I would then get the built-in service provider by calling services.BuildServiceProvider() . In this way I could attempt to resolve from the built-in provider and then, if the resolving bit failed, attempt to resolve from my own container. However, it turns out that the .net core implementation does in fact not use the returned IServiceProvder instance.

The only way I could get this to work was to wire up my own container and use it to resolve my controllers. That could be done by providing an implementation of the IControllerActivator interface.

In this particular implementation I was fiddling with Ninject although I typically prefer Castle but the same applies to any DI container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IKernel>(new StandardKernel());
    services.AddSingleton<IControllerActivator, ControllerActivator>();
}

public class ControllerActivator : IControllerActivator
{
    private readonly IKernel _kernel;

    public ControllerActivator(IKernel kernel)
    {
        Guard.AgainstNull(kernel, nameof(kernel));

        _kernel = kernel;
    }

    public object Create(ControllerContext context)
    {
        return _kernel.Get(context.ActionDescriptor.ControllerTypeInfo.AsType());
    }

    public void Release(ControllerContext context, object controller)
    {
        _kernel.Release(controller);
    }
}

In order to register the controller types I did my DI wiring in the Configure method since I have access to the IApplicationBuilder which can be used to get to the controller types:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
    var kernel = app.ApplicationServices.GetService<IKernel>();

    // kernel registrations

    var applicationPartManager = app.ApplicationServices.GetRequiredService<ApplicationPartManager>();
    var controllerFeature = new ControllerFeature();

    applicationPartManager.PopulateFeature(controllerFeature);

    foreach (var type in controllerFeature.Controllers.Select(t => t.AsType()))
    {
        kernel.Bind(type).ToSelf().InTransientScope();
    }

    applicationLifetime.ApplicationStopping.Register(OnShutdown);

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCors(
        options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
    );

    app.UseMvc();
}

This worked swimmingly for the controllers but resolving "filters" was still a problem given that they use the IFilterFactory on the filter itself to implement a factory method:

public IFilterMetadata CreateInstance (IServiceProvider serviceProvider);

Here we can see that the IServiceProvider implementation is provided in order to resolve any depedencies. This applies when using the TypeFilterAttribute or when defining new filters that inherit from TypeFilterAttribute as I have in my question.

This mechanism is actually a very good example of the difference between "Inversion of Control" and "Dependency Injection". The control lies with the framework (inversion) and we have to provide the relevant implementations. The only issue here is that we are not able to hook in properly since our provided IServiceProvider instance is not passed to the CreateInstance method which then results in a failure when attempting to create an instance of the filter. There are going to be a number of ways to fix this design but we'll leave that to Microsoft.

In order to get my filters working I decided to go the "cross-wiring" route as alluded to by Steven by simply registering the depedencies required by my filters in the services collection also:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IKernel>(new StandardKernel());
    services.AddSingleton<IControllerActivator, ControllerActivator>();

    services.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>();
    services.AddSingleton<IDatabaseGateway, DatabaseGateway>();
    services.AddSingleton<IDatabaseContextCache, ContextDatabaseContextCache>();
    // and so on
}

Since I do not have many dependencies in my filter it works out OK. This does mean that we have "duplicate" registrations that we need to be careful of depending on how the instances are used.

I guess another option may be to forego your DI container of choice and use only the built-in container.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM