Autofac 集成測試 在 ConfigureContainer 之後進行 Mock 注入

在使用 Autofac 框架進行開發後,編寫集成測試時,需要用 Mock 的用於測試的模擬的類型去代替容器裏面已注入的實際類型,也就需要在 Autofac 完全收集完成之後,再次注入模擬的對象進行覆蓋原有業務代碼註冊的正式對象。但 Autofac 默認沒有提供此機制,我閱讀了 Autofac 的源代碼之後,創建了一些輔助代碼,實現了此功能。本文將告訴大家如何在集成測試裏面,在使用了 Autofac 的項目裏面,在所有收集完成之後,注入用於測試的 Mock 類型,和 Autofac 接入的原理

背景

為什麼選擇使用 Autofac 框架?原因是在此前的 WPF 項目裏面,有使用過的是 MEF 和 Autofac 兩個框架,而 MEF 的性能比較糟心。解決 MEF 性能問題的是 VS-MEF 框架。在後續開發的一個 ASP.NET Core 項目裏面,也就自然選用了 Autofac 框架

對比原生的 ASP.NET Core 自帶的 DI 框架,使用 Autofac 的優勢在於支持模塊化的初始化,支持屬性注入

默認的 Autofac 可以通過 Autofac.Extensions.DependencyInjection 將 Autofac 和 dotnet 通用依賴注入框架合入在一起,但在 Autofac 裏面的定製要求是在 Startup 的 ConfigureContainer 函數裏面進行依賴注入,也就是在默認的 ASP.NET Core 裏面沒有提供更靠後的依賴注入方法,可以在完成收集之後,再次注入測試所需要的類型,覆蓋業務代碼裏面的實際對象

需求

假定在一個應用,如 ASP.NET Core 應用裏面,進行集成測試,想要在集成測試裏面,使用項目裏面的依賴注入關係,只是將部分類型替換為測試項目裏面的模擬的類型

而在應用裏面,實際的業務類型是在 Autofac 的 Module 進行注入的。在應用裏面的大概邏輯如下,在 Program 的 CreateHostBuilder 函數裏面通過 UseServiceProviderFactory 方法使用 Autofac 替換 原生 的框架

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                // 使用 auto fac 代替默認的 IOC 容器 
                .UseServiceProviderFactory(new AutofacServiceProviderFactory());

在 Startup 裏面添加 ConfigureContainer 方法,代碼如下

        public void ConfigureContainer(ContainerBuilder builder)
        {
            
        }

在 ConfigureContainer 裏面注入具體的需要初始化的業務模塊,例如 FooModule 模塊。在具體模塊裏面注入實際業務的類型,如 Foo 類型,代碼如下

    public class Startup
    {
    	// 忽略代碼

        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule(new FooModule());
        }
    }

    class FooModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Console.WriteLine($"06 FooModule");

            builder.RegisterType<Foo>().As<IFoo>();
        }
    }

    public class Foo : IFoo
    {
    }

    public interface IFoo
    {
    }

現在的需求是在集成測試裏面,將 IFoo 的實際類型從 Foo 替換為 TestFoo 類型

在集成測試項目裏面,可以使用如下代碼獲取實際的項目的依賴注入收集

                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    // 使用 auto fac 代替默認的 IOC 容器 
                    .UseServiceProviderFactory(new AutofacServiceProviderFactory());

                var host = hostBuilder.Build();

                var foo = host.Services.GetService<IFoo>();

以上的 foo 就是從收集的容器裏面獲取的 IFoo 對象,以上代碼獲取到的是業務代碼的 Foo 類型對象。假定需要讓容器裏面的 IFoo 的實際類型作為測試的 TestFoo 類型,就需要在實際項目的依賴注入收集完成之前,進行測試的注入

但實際上沒有在 Autofac 裏面找到任何的輔助方法可以用來實現此功能。如果是默認的應用框架,可以在 ConfigureWebHostDefaults 函數之後,通過 ConfigureServices 函數覆蓋在 Startup 的 ConfigureServices 函數注入的類型

實現方法

實現的方法是很簡單的,關於此實現為什麼能解決問題還請參閱下文的原理部分

集成測試項目不需要改動原有的業務項目即可完成測試,實現方法是在集成測試項目裏面添加 FakeAutofacServiceProviderFactory 用來替換 Autofac 的 AutofacServiceProviderFactory 類型,代碼如下

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        public FakeAutofacServiceProviderFactory(
            ContainerBuildOptions containerBuildOptions = ContainerBuildOptions.None,
            Action<ContainerBuilder>? configurationActionOnBefore = null,
            Action<ContainerBuilder>? configurationActionOnAfter = null)
        {
            _configurationActionOnAfter = configurationActionOnAfter;
            AutofacServiceProviderFactory =
                new AutofacServiceProviderFactory(containerBuildOptions, configurationActionOnBefore);
        }

        private AutofacServiceProviderFactory AutofacServiceProviderFactory { get; }
        private readonly Action<ContainerBuilder>? _configurationActionOnAfter;

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            _configurationActionOnAfter?.Invoke(containerBuilder);
            return AutofacServiceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

可以看到本質的 FakeAutofacServiceProviderFactory 實現就是通過 AutofacServiceProviderFactory 的屬性實現,只是在 CreateServiceProvider 方法裏面加入了委託,可以方便在單元測試裏面進行注入自己的方法

在集成測試項目裏面的使用方法如下

                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    // 使用 auto fac 代替默認的 IOC 容器 
                    .UseServiceProviderFactory(new FakeAutofacServiceProviderFactory(configurationActionOnAfter:
                        builder =>
                        {
                            builder.RegisterModule<TestModule>();
                        }));

傳入的委託需要注入測試的初始化模塊,也就是 TestModule 需要加入注入,通過上面代碼,可以讓 TestModule 在依賴注入的最後進行初始化。在 TestModule 裏面加入實際的測試類型注入的代碼

    class TestModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<TestFoo>().As<IFoo>();
        }
    }

    class TestFoo : IFoo
    {
    }

通過上面方法就可以讓集成測試項目從容器裏面獲取 IFoo 的對象,拿到的是 TestFoo 類型,集成測試項目的代碼如下

    [TestClass]
    public class FooTest
    {
        [ContractTestCase]
        public void Test()
        {
            "依賴注入的時機,可以在完成收集之後,覆蓋原有的類型".Test(() =>
            {
                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    // 使用 auto fac 代替默認的 IOC 容器 
                    .UseServiceProviderFactory(new FakeAutofacServiceProviderFactory(configurationActionOnAfter:
                        builder =>
                        {
                            builder.RegisterModule<TestModule>();
                        }));

                var host = hostBuilder.Build();

                var foo = host.Services.GetService<IFoo>();
                Assert.IsInstanceOfType(foo, typeof(TestFoo));
            });
        }
    }

    class TestModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<TestFoo>().As<IFoo>();
        }
    }

    class TestFoo : IFoo
    {
    }

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        public FakeAutofacServiceProviderFactory(
            ContainerBuildOptions containerBuildOptions = ContainerBuildOptions.None,
            Action<ContainerBuilder>? configurationActionOnBefore = null,
            Action<ContainerBuilder>? configurationActionOnAfter = null)
        {
            _configurationActionOnAfter = configurationActionOnAfter;
            AutofacServiceProviderFactory =
                new AutofacServiceProviderFactory(containerBuildOptions, configurationActionOnBefore);
        }

        private AutofacServiceProviderFactory AutofacServiceProviderFactory { get; }
        private readonly Action<ContainerBuilder>? _configurationActionOnAfter;

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            _configurationActionOnAfter?.Invoke(containerBuilder);
            return AutofacServiceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

以上集成測試使用了 CUnit 中文單元測試框架輔助,在上面代碼裏面,可以看到集成測試裏面的容器拿到的 IFoo 對象就是 TestFoo 類型。通過這個方法就可以在業務代碼執行過程,注入測試需要的類型

為什麼通過以上的代碼即可實現此功能,為什麼需要自己實現一個 FakeAutofacServiceProviderFactory 類型,為什麼不能在 AutofacServiceProviderFactory.CreateServiceProvider 方法之前注入類型,而是需要再定義一個 TestModule 模塊,在測試初始化模塊進行初始化。更多細節請看下文

原理

回答以上問題,需要了解各個注入方法調用的順序,我在代碼裏面通過控制台輸出各個方法的順序。標記了順序的代碼放在本文最後

以下是按照調用順序的方法代碼

Startup 的 ConfigureServices 方法

    public class Startup
    {
        // 忽略代碼

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit //go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            Console.WriteLine($"01 ConfigureServices");
        }
    }

在 Startup 的 ConfigureServices 是依賴注入中最開始調用的方法,這也是原生的框架自帶的方法

IHostBuilder 的 ConfigureServices 擴展方法

                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    .ConfigureServices(collection => Console.WriteLine($"02 ConfigureServices Delegate"))

在 IHostBuilder 的 ConfigureServices 擴展方法將會在 Startup 的 ConfigureServices 方法執行完成之後調用,因此如果只使用原生的依賴注入,可以在此方法進行覆蓋,也就是如果沒有使用 Autofac 框架,只使用原生的框架,可以在集成測試,在此方法注入測試的類型

Startup 的 ConfigureContainer 方法

    public class Startup
    {
        // 忽略代碼

        public void ConfigureContainer(ContainerBuilder builder)
        {
            Console.WriteLine($"03 ConfigureContainer");
            builder.RegisterModule(new FooModule());
        }
    }

可以看到 public void ConfigureContainer(ContainerBuilder builder) 方法的調用在 ConfigureServices 方法之後,在 Autofac 也通過此機制實現代替原生的依賴注入功能,也通過此方法從原生的注入獲取依賴

關於 Autofac 的實際邏輯,請參閱下文

FakeAutofacServiceProviderFactory 的 CreateServiceProvider 方法

在如上代碼,咱編寫了 FakeAutofacServiceProviderFactory 用於替換 AutofacServiceProviderFactory 類型。在 FakeAutofacServiceProviderFactory 的 CreateServiceProvider 方法將會在調用 ConfigureContainer 之後執行

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        public FakeAutofacServiceProviderFactory(
            ContainerBuildOptions containerBuildOptions = ContainerBuildOptions.None,
            Action<ContainerBuilder>? configurationActionOnBefore = null,
            Action<ContainerBuilder>? configurationActionOnAfter = null)
        {
            _configurationActionOnAfter = configurationActionOnAfter;
            AutofacServiceProviderFactory =
                new AutofacServiceProviderFactory(containerBuildOptions, configurationActionOnBefore);
        }

        private AutofacServiceProviderFactory AutofacServiceProviderFactory { get; }
        private readonly Action<ContainerBuilder>? _configurationActionOnAfter;

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            Console.WriteLine($"04 FakeAutofacServiceProviderFactory");
            _configurationActionOnAfter?.Invoke(containerBuilder);
            return AutofacServiceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

在以上的 CreateServiceProvider 方法將會在 Startup 的 ConfigureContainer 方法之後執行,如上面代碼。按照上面代碼,將會執行 _configurationActionOnAfter 委託

因此下一個執行的就是傳入的委託

                IHostBuilder? hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    .ConfigureServices(collection => Console.WriteLine($"02 ConfigureServices Delegate"))
                    // 使用 auto fac 代替默認的 IOC 容器 
                    .UseServiceProviderFactory(new FakeAutofacServiceProviderFactory(configurationActionOnAfter:
                        builder =>
                        {
                            Console.WriteLine($"05 ConfigurationActionOnAfter");
                            builder.RegisterModule<TestModule>();
                        }));

也就是如上代碼的 ConfigurationActionOnAfter 委託

但儘管 FakeAutofacServiceProviderFactory 的 CreateServiceProvider 在 Startup 的 ConfigureContainer 方法之後執行,實際上很多開發者不會在 Startup 的 ConfigureContainer 方法完成註冊,而是在 ConfigureContainer 裏面初始化模塊。如上面代碼,在業務邏輯註冊的模塊的初始化還沒被調用。只有在實際的 ContainerBuilder 調用 Build 方法,才會執行模塊的 Load 方法

因此下一個調用就是業務邏輯註冊的模塊

FooModule 的 Load 方法

按照 Autofac 的定義,在 ConfigureContainer 的 Build 方法裏面,才會執行模塊的初始化,調用 Load 方法

    class FooModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Console.WriteLine($"06 FooModule");

            builder.RegisterType<Foo>().As<IFoo>();
        }
    }

在 Autofac 裏面,將會按照模塊註冊的順序,調用模塊的 Load 方法,如上面代碼,可以看到 TestModule 在最後被註冊,因此將會最後執行

TestModule 的 Load 方法

在上面代碼 TestModule 是最後註冊到 Autofac 的模塊,也就是 TestModule 將會最後被執行

    class TestModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Console.WriteLine($"07 TestModule");

            builder.RegisterType<TestFoo>().As<IFoo>();
        }
    }

如上面代碼,在 TestModule 注入的測試類型,將會替換業務代碼的實際類型

Autofac 接入的方法

通過上面的方法調用順序,大家也可以了解為什麼集成測試的代碼這樣寫就有效果。更深入的邏輯是 Autofac 的設計,為什麼可以讓 Autofac 框架可以接入到 ASP.NET Core 應用裏面,我在此前可一直都是在 WPF 框架使用的。這個問題其實很簡單,所有的 dotnet 項目,無論是 ASP.NET Core 還是 WPF 等,都是相同的 dotnet 邏輯,裝配方式都相同,只是頂層業務邏輯實現方法有所不同,因此只需要加一點適配邏輯就能通用

從上面項目安裝的 NuGet 包可以看到,安裝了 Autofac.Extensions.DependencyInjection 庫就是提供 Autofac 與 dotnet 通用依賴注入框架鏈接的功能,而 ASP.NET Core 原生的框架就是基於 dotnet 通用依賴注入框架,因此就能將 Autofac 接入到 ASP.NET Core 應用

在 UseServiceProviderFactory 方法裏面,將會執行 ASP.NET Core 框架的依賴注入容器相關方法,此方法注入的 IServiceProviderFactory 帶泛形的類型,將可以支持在 Startup 方法裏面添加 ConfigureContainer 方法,參數就是 IServiceProviderFactory 的泛形

如加入了 FakeAutofacServiceProviderFactory 類型,此類型繼承了 IServiceProviderFactory<ContainerBuilder> 接口,也就是 IServiceProviderFactory 的 泛形 是 ContainerBuilder 類型,因此可以在 Startup 的 ConfigureContainer 方法參數就是 ContainerBuilder 類型

    public class Startup
    {
        // 忽略代碼

        public void ConfigureContainer(ContainerBuilder builder)
        {
          
        }
    }

而 ConfigureContainer 將會被 Microsoft.AspNetCore.Hosting.GenericWebHostBuilder 進行調用,在 GenericWebHostBuilder 的調用順序是先調用 ConfigureServices 再調用 對應的 ConfigureContainer 方法

在 Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider 方法就是實際創建容器的方法,這個方法裏面,將會先調用完成 ConfigureServices 的配置,然後再調用 ConfigureContainer 的配置,代碼如下

    public class HostBuilder : IHostBuilder
    {
        private void CreateServiceProvider()
        {
           // 忽略代碼
            var services = new ServiceCollection();

            foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
            {
                configureServicesAction(_hostBuilderContext, services);
            }

            object containerBuilder = _serviceProviderFactory.CreateBuilder(services);

            foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
            {
                containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
            }

            _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

此時的 _serviceProviderFactory 將會是注入的 FakeAutofacServiceProviderFactory 類型,將會調用對應的 CreateBuilder 方法,也就是如下代碼將會調用

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        // 忽略代碼
        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }
    }

在 HostBuilder 的 _configureContainerActions 委託調用 ConfigureContainer 的邏輯,實際就是 Startup 類型裏面定義的 ConfigureContainer 方法

因此就是先調用 Startup 類型和 IHostBuilder 的 ConfigureServices 方法,然後再調用 ConfigureContainer 方法

在 Autofac 的 AutofacServiceProviderFactory 在 CreateBuilder 方法就可以拿到了原生註冊的所有類型,因為在調用 CreateBuilder 之前已經完成了所有的原生邏輯

在 AutofacServiceProviderFactory 的 CreateBuilder 方法將會先創建 ContainerBuilder 對象,然後調用 Populate 方法,從原生的 IServiceCollection 獲取註冊的類型,重新放到 ContainerBuilder 容器

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            var builder = new ContainerBuilder();

            builder.Populate(services);

            _configurationAction(builder);

            return builder;
        }

上面代碼的 ContainerBuilder 是 Autofac 框架的,而 Populate 是擴展方法,和 AutofacServiceProviderFactory 都是在 Autofac.Extensions.DependencyInjection 庫提供的,通過此擴展方法和 AutofacServiceProviderFactory 即可實現 Autofac 和 dotnet 原生接入。在 Populate 方法從 dotnet 原生拿到註冊的類型,放入到 Autofac 的 ContainerBuilder 里,這樣所有之前使用 dotnet 原生注入的類型就可以在 Autofac 拿到

        public static void Populate(
            this ContainerBuilder builder,
            IEnumerable<ServiceDescriptor> descriptors,
            object lifetimeScopeTagForSingletons = null)
        {
            if (descriptors == null)
            {
                throw new ArgumentNullException(nameof(descriptors));
            }

            builder.RegisterType<AutofacServiceProvider>().As<IServiceProvider>().ExternallyOwned();
            builder.RegisterType<AutofacServiceScopeFactory>().As<IServiceScopeFactory>();

            Register(builder, descriptors, lifetimeScopeTagForSingletons);
        }

以上的 IEnumerable<ServiceDescriptor> 就是 IServiceCollection 類型的對象,實際代碼是 Register 裏面拿到註冊的類型,重新放入到 Autofac 里

        private static void Register(
            ContainerBuilder builder,
            IEnumerable<ServiceDescriptor> descriptors,
            object lifetimeScopeTagForSingletons)
        {
            foreach (var descriptor in descriptors)
            {
                if (descriptor.ImplementationType != null)
                {
                    // Test if the an open generic type is being registered
                    var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo();
                    if (serviceTypeInfo.IsGenericTypeDefinition)
                    {
                        builder
                            .RegisterGeneric(descriptor.ImplementationType)
                            .As(descriptor.ServiceType)
                            .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons);
                    }
                    else
                    {
                        builder
                            .RegisterType(descriptor.ImplementationType)
                            .As(descriptor.ServiceType)
                            .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons);
                    }
                }
                else if (descriptor.ImplementationFactory != null)
                {
                    var registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, (context, parameters) =>
                        {
                            var serviceProvider = context.Resolve<IServiceProvider>();
                            return descriptor.ImplementationFactory(serviceProvider);
                        })
                        .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons)
                        .CreateRegistration();

                    builder.RegisterComponent(registration);
                }
                else
                {
                    builder
                        .RegisterInstance(descriptor.ImplementationInstance)
                        .As(descriptor.ServiceType)
                        .ConfigureLifecycle(descriptor.Lifetime, null);
                }
            }
        }

上面代碼拿到的 ServiceDescriptor 就是在原生框架裏面的注入類型的定義,可以看到這些都重新放到 Autofac 的容器裏面

這就是為什麼 Autofac 能拿到在 ASP.NET Core 框架裏面其他框架注入的類型的代碼

在 HostBuilder 的 CreateServiceProvider 方法最後就是調用 IServiceProviderFactory 的 CreateServiceProvider 方法返回實際的容器

也就是調用了 Autofac 的 CreateServiceProvider 方法,代碼如下

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            if (containerBuilder == null) throw new ArgumentNullException(nameof(containerBuilder));

            var container = containerBuilder.Build(_containerBuildOptions);

            return new AutofacServiceProvider(container);
        }

可以看到本質就是調用了 ContainerBuilder 的 Build 方法,而在 Build 方法裏面,才會初始化 Autofac 的模塊。因此在 FakeAutofacServiceProviderFactory 的 CreateServiceProvider 方法裏面添加的代碼,是不會在具體業務模塊的初始化模塊調用之前被調用。但在 Autofac 裏面,模塊的初始化順序是模塊加入 Autofac 的順序,因此可以在 FakeAutofacServiceProviderFactory 裏面再加入測試的模塊,測試的模塊將會是最後加入的模塊,也就是將會最後被執行

因此想要在接入 Autofac 框架覆蓋業務邏輯註冊的類型,就需要在 Autofac 裏面註冊一個測試使用的模塊,要求這個模塊最後註冊,然後在此模塊裏面進行註冊類型,這樣就可以讓測試模塊註冊的類型是最後註冊的,覆蓋原有的類型。而想要讓測試模塊最後註冊,就需要自己實現一個繼承 IServiceProviderFactory<ContainerBuilder> 的類型,才能在 AutofacServiceProviderFactory 的 CreateServiceProvider 方法調用之前註冊模塊

雖然我很喜歡使用 Autofac 框架,但是我覺得在接入 ASP.NET Core 時,沒有很好加入測試的機制,而讓開發者需要自己理解底層的邏輯才能進行註冊測試的類型

這裡也需要給 dotnet 的設計點贊,在一開始的 ASP.NET Core 選擇依賴注入框架時,選擇的是 dotnet 通用依賴注入框架,而 dotnet 通用依賴注入框架最底層的是使用最初的裝配器接口,在 C# 語言裏面接口的定義是最通用的,接口只約束而不定義。通過這一套傳承的定義,可以讓 10 多年前的 Autofac 框架依然可以跑在現代的應用裏面

這 10 多年也不是 Autofac 啥都不做,上面的說法只是為了說明 dotnet 的兼容性特彆強和最初的 dotnet 設計大佬的強大

本文的實現方法,雖然代碼很少,但要理解 dotnet 的依賴注入和 ASP.NET Core 的依賴注入使用,和 Autofac 的接入方法。看起來就是 Autofac 的接入機制其實沒有考慮全,當然,也許是我的技術不夠,也許有更好的實現方法,還請大佬們教教我

代碼

本文所有代碼放在 githubgitee 歡迎小夥伴訪問