.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

posted @ 2022-03-15 13:14  没有星星的夏季  阅读(1045)  评论(0编辑  收藏  举报