• .net core实现带名称的服务(自定义、使用Autofac)


      .net core实现了依赖注入,虽然可以满足大部分的场景了,但是还是有许多不足,其中之一就是实现带名称服务的依赖注入。

      举个例子,比如有下面的接口和它的实现类:  

        public interface IPerson
        {
            string Say();
        }
        public class Person1 : IPerson
        {
            public virtual string Say()
            {
                return nameof(Person1);
            }
        }
        public class Person2 : IPerson
        {
            public virtual string Say()
            {
                return nameof(Person2);
            }
        }

      然后我们在Startup的ConfigureServices中添加服务:  

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPerson, Person1>();
            services.AddTransient<IPerson, Person2>();
    
            ...
        }

      但是当我们注入IPerson服务时,每次得到的都是Person2,因为Person2是比Person1后添加的,想获取到Person1,我们需要使用其它方法,比如先得到所有IPerson的实现,然后做一个过滤:  

        var person = serviceProvider.GetService<IPerson>();//每次都是Person2
        
        var persons= serviceProvider.GetServices<IPerson>();//所有IPerson接口的实现:IEnumerable<IPerson>
        var person1 = persons.FirstOrDefault(p => p is Person1);//过滤得到Person1

      这种方式肯定不是我们想要的,那有没有方法直接获取Person1?或者说我们能不能给的IPerson的实现类一个名称,注入的时候使用这个名称来决定注入那个服务的实现?

      然而很可惜,.net core并没有提供带名称的服务注入功能,但是我们可以自己集成实现,有两种方式:自定义实现,或者采用第三方插件(如Autofac)

      注:以下内容基于.netcore 5.0.14

      自定义方式

      由于.net core的以来注入本质上是基于服务类型的比较(源码见CallSiteFactory的TryCreateOpenGeneric方法TryCreateExact方法),所以我们可以从这个角度出发,对Type多一个包装:

      先创建一个NamedType:  

        internal sealed class NamedType : TypeDelegator
        {
            object name;
            public NamedType(object name)
            {
                this.name = name;
            }
            public NamedType(object name, Type delegatingType) : base(delegatingType)
            {
                this.name = name;
            }
    
    
            public override string Name { get => (name?.GetType() ?? typeof(NamedType)).Name; }
            public override Guid GUID { get; } = Guid.NewGuid();
            public override bool Equals(object obj) => Equals(obj as NamedType);
            public override int GetHashCode() => name.GetHashCode();
            public override bool Equals(Type o) => o is NamedType t && object.Equals(t.name, this.name) && (t.ServiceType == null || ServiceType == null || t.ServiceType == ServiceType);
            public override string FullName => (name?.GetType() ?? typeof(NamedType)).FullName;
    
            public Type ServiceType => typeImpl;
        }

      这个NamedType继承于TypeDelegator,方便我们自定义封装。

      接着添加一系列的拓展方法,用于添加服务:  

      
        public static class ServiceCollectionExtensions
        {
            #region NamedScoped
            public static IServiceCollection AddNamedScoped(this IServiceCollection services, object name, Type serviceType)
                => services.AddScoped(new NamedType(name, serviceType));
            public static IServiceCollection AddNamedScoped(this IServiceCollection services, object name, Type serviceType, Func<IServiceProvider, object> implementationFactory)
                => services.AddScoped(new NamedType(name, serviceType), implementationFactory);
            public static IServiceCollection AddNamedScoped(this IServiceCollection services, object name, Type serviceType, Type implementationType)
                => services.AddScoped(new NamedType(name, serviceType), implementationType);
            public static IServiceCollection AddNamedScoped<TService>(this IServiceCollection services, object name) where TService : class
                => services.AddNamedScoped(name, typeof(TService));
            public static IServiceCollection AddNamedScoped<TService>(this IServiceCollection services, object name, Func<IServiceProvider, TService> implementationFactory) where TService : class
                => services.AddNamedScoped(name, typeof(TService), sp => implementationFactory.Invoke(sp));
            public static IServiceCollection AddNamedScoped<TService, TImplementation>(this IServiceCollection services, object name)
                where TService : class
                where TImplementation : class, TService
                => services.AddNamedScoped(name, typeof(TService), typeof(TImplementation));
            public static IServiceCollection AddNamedScoped<TService, TImplementation>(this IServiceCollection services, object name, Func<IServiceProvider, TImplementation> implementationFactory)
                where TService : class
                where TImplementation : class, TService
                => services.AddNamedScoped(name, typeof(TService), sp => implementationFactory.Invoke(sp));
            #endregion
            #region NamedSingleton
            public static IServiceCollection AddNamedSingleton(this IServiceCollection services, object name, Type serviceType, Type implementationType)
                => services.AddSingleton(new NamedType(name, serviceType), implementationType);
            public static IServiceCollection AddNamedSingleton(this IServiceCollection services, object name, Type serviceType, object implementationInstance)
                => services.AddSingleton(new NamedType(name, serviceType), implementationInstance);
            public static IServiceCollection AddNamedSingleton(this IServiceCollection services, object name, Type serviceType, Func<IServiceProvider, object> implementationFactory)
                => services.AddSingleton(new NamedType(name, serviceType), implementationFactory);
            public static IServiceCollection AddNamedSingleton(this IServiceCollection services, object name, Type serviceType)
                => services.AddSingleton(new NamedType(name, serviceType));
            public static IServiceCollection AddNamedSingleton<TService>(this IServiceCollection services, object name) where TService : class
                => services.AddNamedSingleton(name, typeof(TService));
            public static IServiceCollection AddNamedSingleton<TService>(this IServiceCollection services, object name, Func<IServiceProvider, TService> implementationFactory) where TService : class
                => services.AddNamedSingleton(name, typeof(TService), sp => implementationFactory.Invoke(sp));
            public static IServiceCollection AddNamedSingleton<TService>(this IServiceCollection services, object name, TService implementationInstance) where TService : class
                => services.AddNamedSingleton(name, typeof(TService), implementationInstance);
            public static IServiceCollection AddNamedSingleton<TService, TImplementation>(this IServiceCollection services, object name, Func<IServiceProvider, TImplementation> implementationFactory)
                where TService : class
                where TImplementation : class, TService
                => services.AddNamedSingleton(name, typeof(TService), sp => implementationFactory.Invoke(sp));
            public static IServiceCollection AddNamedSingleton<TService, TImplementation>(this IServiceCollection services, object name)
                where TService : class
                where TImplementation : class, TService
                => services.AddNamedSingleton(name, typeof(TService), typeof(TImplementation));
            #endregion
            #region NamedTransient
            public static IServiceCollection AddNamedTransient(this IServiceCollection services, object name, Type serviceType)
                => services.AddTransient(new NamedType(name, serviceType));
            public static IServiceCollection AddNamedTransient(this IServiceCollection services, object name, Type serviceType, Func<IServiceProvider, object> implementationFactory)
                => services.AddTransient(new NamedType(name, serviceType), implementationFactory);
            public static IServiceCollection AddNamedTransient(this IServiceCollection services, object name, Type serviceType, Type implementationType)
                => services.AddTransient(new NamedType(name, serviceType), implementationType);
            public static IServiceCollection AddNamedTransient<TService>(this IServiceCollection services, object name) where TService : class
                => services.AddNamedTransient(name, typeof(TService));
            public static IServiceCollection AddNamedTransient<TService>(this IServiceCollection services, object name, Func<IServiceProvider, TService> implementationFactory) where TService : class
                => services.AddNamedTransient(name, typeof(TService), sp => implementationFactory.Invoke(sp));
            public static IServiceCollection AddNamedTransient<TService, TImplementation>(this IServiceCollection services, object name)
             where TService : class
             where TImplementation : class, TService
                => services.AddNamedTransient(name, typeof(TService), typeof(TImplementation));
            public static IServiceCollection AddNamedTransient<TService, TImplementation>(this IServiceCollection services, object name, Func<IServiceProvider, TImplementation> implementationFactory)
             where TService : class
             where TImplementation : class, TService
                => services.AddNamedTransient(name, typeof(TService), sp => implementationFactory.Invoke(sp));
            #endregion
        }
    ServiceCollectionExtensions

      为了配合使用,我们还需要一系列的获取服务实现的拓展方法:  

      
        public static class ServiceProviderExtensions
        {
            /// <summary>
            /// 获取命名服务
            /// </summary>
            /// <param name="provider"></param>
            /// <param name="name">服务名称</param>
            /// <returns></returns>
            public static object GetNamedService(this IServiceProvider provider, object name)
                => provider.GetService(new NamedType(name));
            /// <summary>
            /// 获取命名服务
            /// </summary>
            /// <param name="provider"></param>
            /// <param name="name">服务名称</param>
            /// <returns></returns>
            public static object GetRequiredNamedService(this IServiceProvider provider, object name)
                => provider.GetRequiredService(new NamedType(name));
            /// <summary>
            /// 获取命名服务
            /// </summary>
            /// <param name="provider"></param>
            /// <param name="name">服务名称</param>
            /// <param name="serviceType"></param>
            /// <returns></returns>
            public static object GetRequiredNamedService(this IServiceProvider provider, object name, Type serviceType)
                => provider.GetRequiredService(new NamedType(name, serviceType));
            /// <summary>
            /// 获取命名服务
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="provider"></param>
            /// <param name="name">服务名称</param>
            /// <returns></returns>
            public static T GetRequiredNamedService<T>(this IServiceProvider provider, object name)
                => (T)provider.GetRequiredService(new NamedType(name, typeof(T)));
            /// <summary>
            /// 获取命名服务
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="provider"></param>
            /// <param name="name">服务名称</param>
            /// <returns></returns>
            public static T GetNamedService<T>(this IServiceProvider provider, object name)
                => (T)provider.GetService(new NamedType(name, typeof(T)));
            /// <summary>
            /// 获取命名服务
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="provider"></param>
            /// <param name="name">服务名称</param>
            /// <returns></returns>
            public static IEnumerable<T> GetNamedServices<T>(this IServiceProvider provider, string name)
                => provider.GetServices(new NamedType(name, typeof(T))).OfType<T>().ToArray();
            /// <summary>
            /// 获取命名服务
            /// </summary>
            /// <param name="provider"></param>
            /// <param name="name">服务名称</param>
            /// <param name="serviceType"></param>
            /// <returns></returns>
            public static IEnumerable<object> GetNamedServices(this IServiceProvider provider, object name, Type serviceType)
                => provider.GetServices(new NamedType(name, serviceType)).Where(serviceType.IsInstanceOfType).ToArray();
        }
    ServiceProviderExtensions

      然后我们可以在Startup的ConfigureServices中添加具名服务:  

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddNamedTransient<IPerson, Person1>("person1");
            services.AddNamedTransient<IPerson, Person2>("person2");
    
            ...
        }

      相应的,可以使用名称获取对应的服务实现:  

        var person1 = serviceProvider.GetNamedService<IPerson>("person1");// Person1
        var person2 = serviceProvider.GetNamedService<IPerson>("person2");// Person2

      以上自定义方式参考了:https://www.jianshu.com/p/ae8991280fb5,在此基础上有一些拓展,名称不用局限于string类型,而是任何的object类型,这样不仅可以使用一些数值类型作为名称,也可以使用一些复杂类型,总之,只需要这个类型重写了Equals方法就可以了,比如record:  

        public record Named(string FirstName,string LastName); // 一个记录
    
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddNamedTransient<IPerson, Person1>(new Named("zhang","san"));
            services.AddNamedTransient<IPerson, Person2>(new Named("li", "si"));
    
            ...
        }

      可以使用名称获取对应的服务实现:

        var person1 = serviceProvider.GetNamedService<IPerson>(new Named("zhang", "san"));// Person1
        var person2 = serviceProvider.GetNamedService<IPerson>(new Named("li", "si"));// Person2

      甚至,这里还可以使用匿名类:  

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddNamedTransient<IPerson, Person1>(new { a = "zhang", b = "san" });
            services.AddNamedTransient<IPerson, Person2>(new { a = "li", b = "si" });
    
            ...
        }

      使用名称获取对应的服务实现:

        var person1 = serviceProvider.GetNamedService<IPerson>(new { a = "zhang", b = "san" });// Person1
        var person2 = serviceProvider.GetNamedService<IPerson>(new { a = "li", b = "si" });// Person2

      这样,我们可以很轻松的使用多个项作为名称,而不需要把它们转换成单一类型了

      这种自定义的方式已经很好用了,但是有它的不足,因为我们服务无法通过构造函数来使用带名称的服务注入,只能通过serviceProvider来直接获取。接下来说说使用autofac来解决这个问题。

      采用第三方插件(Autofac)

      Autofac是.net最优秀的IOC插件之一,从.net framework开始就有了它的存在。

      开始,我们需要使.net core集成使用Autofac替换掉原身的IOC,方法如下:

      首先,使用nuget安装:Autofac.Extensions.DependencyInjection

      然后修改Program,使用AutofacServiceProviderFactory:  

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

      这样就替换掉.net core原版的IOC了,是不是很简单?

      :在以前.netcore2.x及之前的版本,想替换原版的IOC,只需要在ConfigureServices中返回IServiceProvider对象即可:  

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            ...
    
            var container = new ContainerBuilder();
            container.Populate(services);
            return new AutofacServiceProvider(container);
        }

      从.net core3.x开始,ConfigureServices已经不支持返回IServiceProvider了,需要使用IServiceProviderFactory<TContainerBuilder>接口来指定,比如Autofac对应的实现类是AutofacServiceProviderFactory,所以一般需要在Program中使用它替换默认的IServiceProviderFactory<TContainerBuilder>从而达到替换原版的IOC。而如果我们要使用Autofac的ContainerBuilder,有两种方式:1、使用AutofacServiceProviderFactory实例化时传入configurationAction参数,2、在Startup中添加ConfigureContainer方法,参数就是ContainerBuilder,这也是推荐做法。

      

      回到话题,由于.net core的IServiceCollection不能添加带名称的服务,所以带名称的服务需要使用Autofac的ContainerBuilder来完成,这一点我们可以在Startup的ConfigureContainer中来完成:  

        public class Startup
        {
            ...
    
            public void ConfigureContainer(ContainerBuilder containerBuilder)
            {
                containerBuilder.RegisterType<Person1>().Keyed<IPerson>("person1");
                containerBuilder.RegisterType<Person2>().Keyed<IPerson>("person2");
            }
            
            ...
        }

      如果是动态获取,可能需要将IServiceProvider转换成AutofacServiceProvider,然后使用它的LifetimeScope的ResolveKeyed方法来获取:  

        var autofacServiceProvider = serviceProvider as AutofacServiceProvider;
        //当key是string类型时,Keyed等价于Named
        var person1 = autofacServiceProvider.LifetimeScope.ResolveKeyed<IPerson>("person1");
        var person2 = autofacServiceProvider.LifetimeScope.ResolveNamed<IPerson>("person2");

      如果是在构造函数中使用,需要使用Autofac.Features.AttributeFilters.KeyFilterAttribute指定参数使用哪个key,然后运行启动,直接报错,因为我们还为启用相关配置。

      如果我们的服务类是通过ContainerBuilder添加的,那么需要在添加完成后使用WithAttributeFiltering方法修饰一下,如:

      我们有一个Demo类需要注入IPerson:  

        public class Demo
        {
            public Demo([KeyFilter("person1")] IPerson person1, [KeyFilter("person2")] IPerson person2)
            {
                Console.WriteLine(person1.Say());
                Console.WriteLine(person2.Say());
            }
        }

      那么我们需要在添加Demo的时候:  

        public void ConfigureContainer(ContainerBuilder containerBuilder)
        {
            containerBuilder.RegisterType<Person1>().Keyed<IPerson>("person1");
            containerBuilder.RegisterType<Person2>().Keyed<IPerson>("person2");
    
            //WithAttributeFiltering告诉Autofac在实例化时启用构造函数的参数的特性
            containerBuilder.RegisterType<Demo>().WithAttributeFiltering();
        }

      这样,Demo中的person1和person2才会呗正确赋值。 

      如果我们的服务类是通过IServiceCollection添加的,比如Controller,这样一来我们就无法添加WithAttributeFiltering标识,但是我们可以使用Autofac的Middleware来解决这个问题。

      首先,因为默认情形下,Controller不是交给IOC的Service去实例化的,那么我们首先就得修改这个默认行为,只需要修改ConfigureServices:  

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers().AddControllersAsServices();//AddControllersAsServices表示将Controller的实例化按普通的服务一样去进行
    
            ...
        }

      接着,定义一个管道处理器,它的作用就类似WithAttributeFiltering的作用:  

        public class DefaultResolveMiddleware : IResolveMiddleware
        {
            public PipelinePhase Phase => PipelinePhase.Sharing;
    
            public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
            {
                var parameter = new ResolvedParameter((p, c) =>
                 {
                     var filter = p.GetCustomAttributes<ParameterFilterAttribute>(true).FirstOrDefault();
                     return filter != null && filter.CanResolveParameter(p, c);
                 },
                (p, c) =>
                {
                    var filter = p.GetCustomAttributes<ParameterFilterAttribute>(true).First();
                    return filter.ResolveParameter(p, c);
                });
                var paramters = context.Parameters.ToList();
                paramters.Add(parameter);
                context.ChangeParameters(paramters);//替换掉原来的
    
                next(context);
            }
        }

      这个管道处理器其实就等价于启用WithAttributeFiltering。

      然后在ConfigureContainer中,对所有的Controller使用这个管道处理器:

        public void ConfigureContainer(ContainerBuilder containerBuilder)
        {
            containerBuilder.RegisterType<Person1>().Keyed<IPerson>("person1");
            containerBuilder.RegisterType<Person2>().Keyed<IPerson>("person2");
    
            //WithAttributeFiltering告诉Autofac在实例化时启用构造函数的参数的特性
            containerBuilder.RegisterType<Demo>().WithAttributeFiltering();
    
            this.GetType().Assembly.GetTypes()
                .Where(f => !f.IsAbstract && typeof(ControllerBase).IsAssignableFrom(f)) //得到所有的控制器
                .ToList()
                .ForEach(c =>
                {
                    //对每一个Controller都使用DefaultResolveMiddleware,它的作用等价于使用WithAttributeFiltering声明
                    containerBuilder.RegisterServiceMiddleware(new TypedService(c), new DefaultResolveMiddleware());
                });
        }

      这样,通过IServiceCollection添加的服务也就可以在构造参数中使用带名称的服务了。

      总结

      这里提供了两种方法让.net core支持带名称的服务,各有优缺点:  

        自定义方式:
        优点:不需要依赖第三方插件,实现起来也简单,可以定制自己的需求,满足大部分情形
        缺点:只能通过IServiceProvider来实例化,不支持在构造函数中注入
    
        Autofac实现方式:
        优点:基于Autofac实现的强大依赖注入功能,适合各种需要复杂注入的场景
        缺点:依赖于Autofac第三方插件,而且实现起来稍繁琐,技术上要求更高一些

      参考:https://www.jianshu.com/p/ae8991280fb5

  • 相关阅读:
    HTML5 Canvas编写五彩连珠(1):预览
    SQL SERVER BI 入门:(2) Analysis Service 应用
    HTML5 Canvas编写五彩连珠(6):试玩
    HTML5 Canvas编写五彩连珠(4):动画
    HTML5 Canvas编写五彩连珠(3):设计
    SQL SERVER BI 入门:(1)安装与基础概念
    HTML5 Canvas编写五彩连珠(2):画图
    pip报错“ModuleNotFoundError: No module named 'pip'”
    eWebEditor在ie8下不可用的问题
    推荐:解析“extern”
  • 原文地址:https://www.cnblogs.com/shanfeng1000/p/15975895.html
Copyright © 2020-2023  润新知