代码之家  ›  专栏  ›  技术社区  ›  Eben Roux

在ASP.NET核心中使用自定义ISeviceProvider实现

  •  0
  • Eben Roux  · 技术社区  · 6 年前

    我已经实现了一个实现 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 属性:

    [类型筛选器(typeof(requiressessionattribute))]

    但是,我的属性 继承自 类型过滤器 :

    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 但没有明确的答案。

    关于如何正确提供 ISeviceProvider(ISeviceProvider) 将在整个解决方案中使用的接口?

    1 回复  |  直到 6 年前
        1
  •  2
  •   bviale    5 年前

    即使微软声称 replace the built-in container 这看起来似乎不那么简单,甚至不可能。

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

    微软的指导建议改变 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.BuildServiceProvider() . 这样,我可以尝试从内置提供程序解析,然后,如果解析位失败,则尝试从我自己的容器解析。然而,事实证明.NET核心实现确实 使用返回的 IServiceProvder 实例。

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

    在这个特定的实现中,我在处理ninject,尽管我通常更喜欢castle,但这同样适用于任何DI容器:

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

    为了注册控制器类型,我在 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 或者在定义继承自 类型筛选属性 就像我在问的那样。

    这个机制实际上是“控制反转”和“依赖注入”之间区别的一个很好的例子。控制取决于框架(反转),我们必须提供相关的实现。唯一的问题是我们 自我们提供后,能够正确地钩住 ISeviceProvider(ISeviceProvider) 实例是 传递给 CreateInstance 方法,然后在尝试创建筛选器实例时导致失败。有很多方法可以修复这个设计,但我们会把它留给微软。

    为了让我的过滤器正常工作,我决定按照史蒂文的暗示走“交叉布线”路线,只需在 服务 同时收集:

    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
    }
    

    因为我的过滤器中没有太多的依赖项,所以它可以正常工作。这个 这意味着我们有“重复”的注册,我们需要根据实例的使用方式小心处理这些注册。

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