2018年6月29日 星期五

Autofac 相關

註冊物件


UnitTestContainerFactory.InitExternalRegistrarInstance(builder =>
{

    builder.RegisterInstance(mockDealerApplyRepository.Object)
        .As<IDealerApplyRepository>()
        .SingleInstance();

這個是把IDealerApplyRepository介面註冊時使用 mockDealerApplyRepository 這個已經被new出來的物件實體,然後最後面的SingleInstance指的是每個實作都用同一個物件

    builder.RegisterType<MockDealerApplyRepository>()
        .As<IDealerApplyRepository>()
        .InstancePerLifetimeScope();

這邊的IDealerApplyRepository介面註冊則是使用MockDealerApplyRepository類別,就是使用class宣告

});


另一種使用
如果同一介面需要兩個實作註冊,可以使用
            builder
                .RegisterType<CreateDealerTask>()
                .Keyed<IDealerTask>(DealerTaskActionEnum.Create)
                .InstancePerLifetimeScope();

            builder
                .RegisterType<UpdateDealerTask>()
                .Keyed<IDealerTask>(DealerTaskActionEnum.Update)
                .InstancePerLifetimeScope();

一樣都是IDealerTask介面,不過CreateDealerTask、UpdateDealerTask都可以用
但是這樣在使用時必須宣告清楚

IDealerTask task = this.ResolveKeyed<IDealerTask>(DealerTaskActionEnum.Create);




    /// <summary>
    /// Senao.Admin 單元測試 DI 容器工廠
    /// </summary>
    public class UnitTestContainerFactory : UnitTestContainerFactoryBase<UnitTestContainerFactory>
    {
        /// <summary>
        /// 建構子
        /// </summary>
        protected UnitTestContainerFactory()
        {
        }

        /// <summary>
        /// 建構子,支援額外類型註冊
        /// </summary>
        /// <param name="externalTypeRegister">除了底層共同註冊之外,不同的 TestClass 額外註冊的類別委派</param>
        protected UnitTestContainerFactory(Action<ContainerBuilder> externalTypeRegister) : base(externalTypeRegister)
        {
        }

        /// <summary>
        /// 於子類實作之註冊類型之設定
        /// </summary>
        /// <param name="builder">
        /// Used to build an <see cref="T:Autofac.IContainer"/> from component registrations.
        /// </param>
        protected override void RegisterTypes(ContainerBuilder builder)
        {
            // 此處載入該 solution 引用之 Helper、Repository & Service 專案
            IEnumerable<Assembly> assemblies = Assembly.GetExecutingAssembly()
                                                       .GetReferencedAssemblies()
                                                       .Select(Assembly.Load)
                                                       .Concat(new[]
                                                        {
                                                            Assembly.Load("Clam.Common.Helper"),
                                                            Assembly.Load("Clam.Common.Repository"),
                                                            Assembly.Load("Clam.Common.Service"),
                                                            Assembly.Load("Clam.Common.UnitTest"),                                                          Assembly.Load("Clam.Dealer.Channel.Helper"),
                                                            Assembly.Load("Clam.Dealer.Channel.Repository"),
                                                            Assembly.Load("Clam.Dealer.Channel.Service")
                                                        });

            // 取得 ITypeRegistrar
            IOrderedEnumerable<ITypeRegistrar> registrars = assemblies
                                                           .SelectMany(p => p.ExportedTypes.Where(s => s.IsAssignableTo<ITypeRegistrar>() && !s.IsInterface))
                                                           .Select(p => (ITypeRegistrar)Activator.CreateInstance(p))
                                                           .OrderBy(p => p.Order);

            // 對 Helper、Repository & Service 進行分別註冊
            foreach (ITypeRegistrar registrar in registrars)
            {
                registrar.RegisterTypes(builder);
            }

            builder.RegisterCallback(p =>
            {
                foreach (IComponentRegistration item in p.Registrations)
                {
                    item.LogActualRegistration();
                }
            });

            // 以下進行共用 Mock 物件註冊
            builder
                .RegisterType<ConfigHelper>()
                .As<IConfigHelper>()
                .InstancePerLifetimeScope()
                .LogExpectRegistration();

            builder
                .RegisterType<MockExecutionContext>()
                .As<IExecutionContext>()
                .InstancePerLifetimeScope()
                .LogExpectRegistration();

            builder
                .RegisterType<MockUserContext>()
                .As<IUserContext<UserInfo>>()
                .InstancePerLifetimeScope()
                .LogExpectRegistration();

            builder
                .RegisterType<MockUserContext>()
                .As<IUserContext<IIdentity<string>>>()
                .InstancePerLifetimeScope()
                .LogExpectRegistration();

            builder
                .RegisterType<MockEmailService>()
                .As<IEmailService>()
                .InstancePerLifetimeScope()
                .LogExpectRegistration();

            builder
                .RegisterType<NullLoggerFactory>()
                .As<ILoggerFactory>()
                .SingleInstance()
                .LogExpectRegistration();

            builder
                .RegisterGeneric(typeof(Logger<>))
                .As(typeof(ILogger<>))
                .SingleInstance();

            builder
                .RegisterType<MockTaskProvider>()
                .As<ITaskProvider>()
                .InstancePerLifetimeScope()
                .LogExpectRegistration();
        }
    }




    /// <summary>
    /// 單元測試專用之相依性注入容器抽象工廠
    /// </summary>
    /// <typeparam name="TContainerFactory">具體容器工廠</typeparam>
    public abstract class UnitTestContainerFactoryBase<TContainerFactory>
        where TContainerFactory : UnitTestContainerFactoryBase<TContainerFactory>
    {
        /// <summary>
        /// 單一實例,延遲載入
        /// </summary>
        private static Lazy<TContainerFactory> Instance;

        /// <summary>
        /// 除了底層共同註冊之外,不同的 TestClass 額外註冊的類別委派
        /// </summary>
        private readonly Action<ContainerBuilder> ExternalTypeRegister;

        /// <summary>
        /// DI 容器實例
        /// </summary>
        private IContainer ContainerInstance;

        /// <summary>
        /// 建構子
        /// </summary>
        protected UnitTestContainerFactoryBase()
        {
            this.ExternalTypeRegister = null;
        }

        /// <summary>
        /// 建構子,支援額外類型註冊
        /// </summary>
        /// <param name="externalTypeRegister">除了底層共同註冊之外,不同的 TestClass 額外註冊的類別委派</param>
        protected UnitTestContainerFactoryBase(Action<ContainerBuilder> externalTypeRegister)
        {
            this.ExternalTypeRegister = externalTypeRegister;
        }

        /// <summary>
        /// 取得 Container
        /// </summary>
        /// <returns>IContainer</returns>
        public IContainer Container => this.ContainerInstance ?? (this.ContainerInstance = this.BuildContainer());

        /// <summary>
        /// 建立具備額外類型註冊的 UnitTestContainerBuiler 之單一實例。
        /// <para>在真正呼叫 <see cref="GetInstance"/> 之前,允許重新設定。</para>
        /// </summary>
        /// <param name="externalTypeRegister">除了底層共同註冊之外,不同的 TestClass 額外註冊的類別委派</param>
        public static void InitExternalRegistrarInstance(Action<ContainerBuilder> externalTypeRegister)
        {
            if (Instance != null && Instance.IsValueCreated)
            {
                return;
            }

            // 使用 Reflection 的方式維持繼承下的單例模式
            Instance =
                new Lazy<TContainerFactory>(() => (TContainerFactory)Activator.CreateInstance(
                    typeof(TContainerFactory),
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance,
                    null,
                    new object[] { externalTypeRegister },
                    CultureInfo.InvariantCulture));
        }

        /// <summary>
        /// 取得 UnitTestContainerBuiler 的單一實例,
        /// <para> 若測試類別未呼叫 <see cref="InitExternalRegistrarInstance"/> </para>
        /// 則會取得無額外註冊類型的原始 UnitTestContainerBuiler
        /// </summary>
        /// <returns>UnitTestContainerBuiler</returns>
        public static TContainerFactory GetInstance()
        {
            if (Instance != null)
            {
                return Instance.Value;
            }

            Instance = new Lazy<TContainerFactory>(() => (TContainerFactory)Activator.CreateInstance(
                typeof(TContainerFactory),
                true));

            return Instance.Value;
        }

        /// <summary>
        /// 重設 ContainerBuiler
        /// </summary>
        public static void RestContainerBuilder()
        {
            if (Instance == null || !Instance.IsValueCreated)
            {
                return;
            }

            Instance.Value.Container.Dispose();
            Instance = null;
        }

        /// <summary>
        /// 於子類實作之註冊類型之設定
        /// </summary>
        /// <param name="builder">Used to build an <see cref="T:Autofac.IContainer" /> from component registrations.</param>
        protected abstract void RegisterTypes(ContainerBuilder builder);

        /// <summary>
        /// 建構容器
        /// </summary>
        /// <returns>IContainer</returns>
        private IContainer BuildContainer()
        {
            ContainerBuilder builder = new ContainerBuilder();
            this.RegisterTypes(builder);

            // 註冊不同 test class 所定義之額外類別
            this.ExternalTypeRegister?.Invoke(builder);
            IContainer container = builder.Build();

            return container;
        }
    }