• 依赖注入在 dotnet core 中实现与使用:4. 集成 Autofac


    本示例使用 .net core 5 rc-1 实现。

    1. 添加 Nuget 包引用

    使用 Autofac 当然要添加 Autofac 的 Nuget 包,主要涉及到两个:

    • Autofac.Extensions.DependencyInjection 核心支持包
    • Autofac.Extras.DynamicProxy2 AOP 动态代理支持
      如果不需要动态代理的话,只需要添加第一个即可。
    dotnet add package Autofac.Extensions.DependencyInjection
    

    2. 配置 Autofac

    首先需要需要配置 Autofac 的容器工厂。

    由于需要使用 Autofac 的容器,所以在构建应用程序的时候,需要使用 Autofac 的服务工厂。主程序 Program 中的 CreateHostBuilder() 方法需要增加一行,修改之后如下所示:

     public static IHostBuilder CreateHostBuilder (string[] args) =>
                Host.CreateDefaultBuilder (args)
                .UseServiceProviderFactory (new AutofacServiceProviderFactory ())
                .ConfigureWebHostDefaults (webBuilder => {
                    webBuilder.UseStartup<Startup> ();
                });
    }
    

    然后,需要在 Startup() 中配置服务注册。

    Autofac 的服务工厂会在调用 ConfigureServices() 之后,自动调用名为 ConfigureContainer() 的方法,一般情况下,我们会在这个方法里面使用 Autofac 来注册服务。

    在 Startup 文件中,添加如下的 ConfigureContainer() 方法。ContainerBuilder 是定义在命名空间 Autofac 中的,注意添加对该命名空间的引用。

    using Autofac;
    
    public void ConfigureContainer (ContainerBuilder builder) {
          ......
    }
    

    Autofac 提供了各种注册服务的方法,不是微软的 Addxxx() 方式,而是 Registerxxx() 方式。
    例如,如果我们已经定义了一个 IDbService 接口,而它的实现类型是 DbService。那么,注册服务的形式如下所示:

     // register type, and enable interceptor injection
     builder.RegisterType<DbService> ().As<IDbService> ()
                .InstancePerLifetimeScope ();
    

    DbService 是注册在容器中的实现类型,而 As<IDbService> 是在容器中注册的类型。注入的时候需要使用这个接口类型。InstancePerLifetimeScope() 则是说明它的生命周期是 Scope 类型的。
    可以看到,在 Autofac 中,使用链式调用的方式来完成服务注册。

    3. 使用 Autofac Module 进行注册

    Autofac 提供了一个名为 Module 的概念,它支持将一组相关的服务注册过程进行打包,以简化配置和部署。
    Autofac 提供了名为 Autofac.IModule 接口,以及一个它的抽象实现类型 Autofac.Module。它的核心是 Load() 方法,用来完成服务的注册。我们可以重载它以实现自定义的服务注册,该方法的签名如下:

    protected virtual void Load(
    	ContainerBuilder builder
    )
    

    可以看到该方法提供同样的 ContainerBuilder 参数来提供服务注册的支持。
    这样的话,前面的服务注册可以转移到一个 Autofac 的 Module 中来。
    我们可以定义一个服务注册类,如下所示:

    using Autofac;
    using Microsoft.AspNetCore.Mvc;
    
    public class ServiceAutofacModule : Autofac.Module {
        protected override void Load (ContainerBuilder builder) {
               // register type, and enable interceptor injection
               builder.RegisterType<DbService> ().As<IDbService> ()
                  .InstancePerLifetimeScope ();
        }
    }
    

    然后,将 Startup() 中的 ConfigureContainer() 调整为如下形式,使用 Module 的方式完成服务注册。

    public void ConfigureContainer (ContainerBuilder builder) {
    
                // use autofac module 
                builder.RegisterModule<ServiceAutofacModule>();
    }
    

    Module 的使用详见:https://autofaccn.readthedocs.io/en/latest/configuration/modules.html

    4. 常见的注册方式

    1. 按照类型进行注册

    // register type, and enable interceptor injection
            builder.RegisterType<DbService> ().As<IDbService> ()
                .InstancePerLifetimeScope ();
    

    2. 按已经引用的程序集注册

    var assembly = assembly.Load ("Domain.Services");
            builder.registerAssemblyType (assembly)
                .AsImplementedInterfaces ()
                .InstancePerLifetimeScope ();
    

    3. 注册程序集中的某些服务

    下面的代码中,先取得了 ControllerBase 的类型,然后在当前程序集中查找所有派生自 ControllerBase 的 Api 控制器

        builder.RegisterAssemblyTypes(typeof(Program).Assembly)
            .Where(t => t.Name.EndsWith("Service"))
            .AsImplementedInterfaces()
            .InstancePerLifetimeScope();
    

    5. 使用属性注入

    Autofac 除了支持构造函数注入,还支持属性注入,属性注入会在构造函数注入之后进行。
    必须要注意的是,必须在使用属性注入的服务上进行声明,
    例如,如果 DbService 需要支持属性注入,那么需要在注册该服务的时候进行声明。

            builder.RegisterType<DbService> ().As<IDbService> ()
                .PropertiesAutowired()
                .InstancePerLifetimeScope ();
    

    ASP.NET Core 中,对控制器进行属性注入的特殊处理

    默认情况下,ASP.NET Core 对于控制器并不是从容器中创建的,所以如果你检查容器中的注册,是看不到控制器的注册的。
    为了支持属性注入,需要让 ASP.NET Core 将控制器也注册到容器中。这可以在 AddControllers() 方法之后,调用 AddControllersAsServices() 来实现。

     public void ConfigureServices (IServiceCollection services) {
    
                services.AddControllers()
                    .AddControllersAsServices();
    }
    

    然后,我们需要对控制器添加支持属性注入的声明。

    既可以针对单个的控制器类

    // make property autowire at one controller
    builder.RegisterType<WeatherForecastController>()
                .PropertiesAutowired();
    
    

    也可以针对所有的控制器。

     // make property autowire at all api controller
     var controllerBaseType = typeof (ControllerBase);
     builder.RegisterAssemblyTypes (typeof (Program).Assembly)
                .Where (t => controllerBaseType.IsAssignableFrom (t) &&
                    t != controllerBaseType)
                .PropertiesAutowired ();
    
    

    6. 使用 AOP 动态代理

    使用 AOP 需要如下的 4 个步骤。

    1. 定义拦截器

    拦截器的接口 IInterceptor 定义在命名空间 Castle.DynamicProxy 中,需要注意的是,它需要添加对 NuGet 包 Autofac.Extras.DynamicProxy 的引用。

    dotnet add package Autofac.Extras.DynamicProxy
    

    实现 IInterceptor 接口。

    using Castle.DynamicProxy;
    using System;
    
     public class DbServiceInterceptor:IInterceptor  
        {  
            public virtual void Intercept(IInvocation invocation)  
            {  
                Console.WriteLine($"{DateTime.Now}: Before method execting. ");  
                invocation.Proceed();  
                Console.WriteLine($"{DateTime.Now}: After method exected.");  
            }  
        }
    

    2. 注册拦截器

    拦截器也同样需要注册到容器中。

    // register interceptor
    builder.RegisterType<DbServiceInterceptor> ();
    

    3. 启用拦截器

    需要支持拦截器的服务需要启用拦截器,然后才能使用拦截器。

            // register type, and enable interceptor injection
            builder.RegisterType<DbService> ().As<IDbService> ()
                .EnableInterfaceInterceptors ()
                .InstancePerLifetimeScope ();
    

    可以使用 EnableInterfaceInterceptors() 或者 EnableClassInterceptors() 扩展方法来启用拦截器。

    EnableInterfaceInterceptors() 创建接口代理来执行拦截,而 EnableClassInterceptors() 则创建目标组件的子类来执行拦截。

    4. 使用拦截器

    第一种方式是在使用拦截器的服务上,通过特性来声明使用的拦截器。

    using Autofac.Extras.DynamicProxy;
    using Castle.DynamicProxy;
    
    [Intercept (typeof (DbServiceInterceptor))]
    public class DbService : IDbService {
    
        public string Say () {
            return "Hello";
        }
    }
    

    当使用特性来关联拦截器的时候,不需要在注册服务的时候指定拦截器。你只需要启用,实际的拦截器将被自动发现。

    第二种方式是在注册服务的时候指定,使用 InterceptedBy() 扩展方法。

    builder.RegisterType<SomeType>()
           .EnableClassInterceptors()
           .InterceptedBy(typeof(CallLogger));
    

    注意:

    • 使用公共接口
    • 类拦截要求被拦截的方法是虚方法,因为使用了子类代理技术。
    • 通过表达式创建的服务,或者使用实例注册的服务,不能使用子类方式代理,此时,要使用接口代理。
    • 要使用接口代理,服务必须仅仅通过接口提供服务,为了最佳的性能,所有此类服务接口必须是注册的一部分,例如使用 .As 子句。
    • 如果通过 EnableClassInterceptors() 使用了类拦截,则避免使用构造函数选择器 UsingConstructor()。在使用类拦截的时候,会为代理类生成新的构造函数以获取你希望使用的拦截器。如果你使用了 UsingConstructor(),就会跳过此逻辑。导致拦截器不能被使用。

    已知问题:

    • 同步方法拦截。Castle 拦截器仅仅支持同步方法拦截。不支持显式的 async/await 方法。但是,async/await 是 Task 的语法糖,你可以在拦截器中使用 Task 和 ContinueWith() 之类的方法。 This issue 展示了用法。另外,这些助手类 也使得 async 工作更容易一点。
    • Castle.Core 版本问题。

    参考资料

  • 相关阅读:
    关于CSS自文档的思考_css声明式语言式代码注释
    html5中contenteditable属性如果过滤标签,过滤富文本样式
    web前端工程化/构建自动化
    Python连载19-装饰器
    Java连载1-概述&常用的dos命令
    HTML连载18-id选择器与class区别&class选择器使用思路&后代选择器
    Python连载18-closure闭包解释及其注意点
    HTML连载17-id选择器&类选择器
    Python连载17-排序函数&返回函数的函数
    HTML连载16-颜色控制属性2&标签选择器
  • 原文地址:https://www.cnblogs.com/haogj/p/13683730.html
Copyright © 2020-2023  润新知