.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 }
為了配合使用,我們還需要一系列的獲取服務實現的拓展方法:

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(); }
然后我們可以在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第三方插件,而且實現起來稍繁瑣,技術上要求更高一些