zoukankan      html  css  js  c++  java
  • 依赖注入在 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 版本问题。

    参考资料

  • 相关阅读:
    leetcode 13. Roman to Integer
    python 判断是否为有效域名
    leetcode 169. Majority Element
    leetcode 733. Flood Fill
    最大信息系数——检测变量之间非线性相关性
    leetcode 453. Minimum Moves to Equal Array Elements
    leetcode 492. Construct the Rectangle
    leetcode 598. Range Addition II
    leetcode 349. Intersection of Two Arrays
    leetcode 171. Excel Sheet Column Number
  • 原文地址:https://www.cnblogs.com/haogj/p/13683730.html
Copyright © 2011-2022 走看看