在asp.net核心中使用自定义IServiceProvider实现

时间:2018-05-23 04:55:40

标签: c# asp.net-core dependency-injection

我已经实现了一个实现IServiceProvider的适配器,并从ConfigureServices类中的Startup.方法返回它:

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

    var container = new NinjectComponentContainer(kernel);

    // ...

    return ServiceProviderFactory.Create(container, services);
}

但是,我的实现似乎并未在所有地方使用。我甚至试图覆盖IHttpContextAccessor以返回修改后的HttpContext

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

            result.RequestServices = _serviceProvider;

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

为了测试我是否可以实现我的实现,我使用过滤器来查看HttpContext.RequestServices将返回的内容:

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

context.HttpContext.RequestServices返回的类型是:

enter image description here

我的主要问题是尝试在过滤器的构造函数中解析已注册的组件,但似乎总是失败,说组件未注册。但是, 在使用TypeFilter属性

时似乎可以正常工作

[TypeFilter(typeof运算(RequiresSessionAttribute))]

但是,我的属性 继承自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;
        }

我确实遇到过this 个问题,但没有明确的答案。

有关如何正确提供将在整个解决方案中使用的IServiceProvider接口的自定义实现的任何想法?

1 个答案:

答案 0 :(得分:2)

尽管微软表示有可能replace the built-in container,但它似乎不是那么简单,甚至可能。

正如Steven在他的第一条评论中所述,如果您选择使用您选择的容器,您应该并排运行它们。

Microsoft的指导意见建议更改ConfigureServices类中的Startup

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

以下内容:

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

    // registrations into container

    return new YourContainerAdapter(container);
}

但是,由于services中已有框架注册我们不一定知道如何在我们自己的容器中重新注册,因此存在许多问题。好吧,有一个描述符,所以如果我们的容器支持所有各种方法,那么实际上可以重新注册所有组件。各种DI容器在注册和服务解析时具有不同的机制。他们中的一些人在两者之间有很大的区别,因此有时候很难适应“常见”的解决方案。

我最初的想法是提供一个接受两者我自己的容器的适配器以及services集合,然后我将通过调用{{{{}}来获取内置服务提供者1}}。通过这种方式,我可以尝试从内置提供程序解析,然后,如果解析位失败,则尝试从我自己的容器中解析。但是,事实证明.net核心实现实际上使用返回的services.BuildServiceProvider()实例。

我能让这个工作的唯一方法是连接我自己的容器并用它来解析我的控制器。这可以通过提供IServiceProvder接口的实现来完成。

在这个特定的实现中,我正在摆弄Ninject,虽然我通常更喜欢Castle,但同样适用于任何DI容器:

IControllerActivator

为了注册控制器类型,我在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); } } 方法中进行了我的DI接线,因为我可以访问可用于访问控制器类型的Configure

IApplicationBuilder

这适用于控制器,但解决“过滤器”仍然是一个问题,因为他们使用过滤器本身的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(); } 来实现工厂方法:

IFilterFactory

在这里,我们可以看到提供了public IFilterMetadata CreateInstance (IServiceProvider serviceProvider); 实现以解决任何依赖关系。这在使用IServiceProvider或定义从TypeFilterAttribute继承的新过滤器时适用,就像我在我的问题中所做的那样。

这种机制实际上是“控制反转”和“依赖注入”之间差异的一个很好的例子。控制在于框架(反转),我们必须提供相关的实现。这里唯一的问题是我们能够正确挂钩,因为我们提供的TypeFilterAttribute实例是而不是传递给IServiceProvider方法然后尝试创建过滤器实例时导致失败。有很多方法可以解决这个问题,但我们会把它留给微软。

为了让我的过滤器正常工作,我决定采用Steven所提到的“交叉布线”路线,只需在CreateInstance集合中注册我的过滤器所需的依赖关系:

services

由于我的过滤器中没有很多依赖项,因此可以正常运行。 意味着我们需要注意“重复”注册,具体取决于实例的使用方式。

我想另一个选择可能是放弃你选择的DI容器,只使用内置容器。