1. 程式人生 > 其它 >.net core實現帶名稱的服務(自定義、使用Autofac)

.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");
        }
        
        ...
    }

  然後在建構函式中使用即可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