.NET 8新特性之KeyedService
简介
.NET 8 在 Preview 7 中引入了 KeyedService 支持,以后我们可以方便支持按 name 来获取 service 了,有些情况下就不用自己创建一个 factory 了。
例子
GetStarted
来看使用一个基本的使用示例:
输出结果如下:
另外如果我们注册 keyed service 的时候使用 null 作为 serviceKey,实际相当于注册了一个非 keyed service,比如说这两种注册方式是等价的
ServiceKey in constructor
在构造方法中可以使用 ServiceKeyAttribute 来在构造方法中获取注册的 serviceKey,我们来看一个示例:
这里需要注意的是我们需要保证 constructor 中的 serviceKey 类型和获取服务时的类型应该是一致的,否则会有异常,比如:
Scoped Sevice
目前对于 scoped service 的支持是有些问题的,使用 scoped service 使用会发生异常
感兴趣的可以尝试一下下面的示例,看看两个 API 的 response:
其它
我们也可以结合 Options 来方便的实现基于 options 的 named service,示例如下:
.NET 8 在 Preview 7 中引入了 KeyedService 支持,以后我们可以方便支持按 name 来获取 service 了,有些情况下就不用自己创建一个 factory 了。
例子
GetStarted
来看使用一个基本的使用示例:
codeduidaima.com
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddKeyedSingleton<IUserIdProvider, EnvironmentUserIdProvider>("env");
- serviceCollection.AddKeyedSingleton<IUserIdProvider, NullUserIdProvider>("");
- // 堆代码 duidaima.com
- using var services = serviceCollection.BuildServiceProvider();
- var userIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("");
- Console.WriteLine(userIdProvider.GetUserId());
- var envUserIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("env");
- Console.WriteLine(envUserIdProvider.GetUserId());
- file interface IUserIdProvider
- {
- string GetUserId();
- }
- file sealed class EnvUserIdProvider: IUserIdProvider
- {
- public string GetUserId() => Environment.MachineName;
- }
- file sealed class NullUserIdProvider: IUserIdProvider
- {
- public string GetUserId() => "(null)";
- }
codeduidaima.com
- (null)
- WEIHANLI-SURFACE
AnyKey
serviceKey 有一个特殊的存在 KeyedService.AnyKey 我们可以用这个来捕获未注册的 serviceKey,示例如下:
codeduidaima.com
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddKeyedSingleton<IUserIdProvider, NullUserIdProvider>(KeyedService.AnyKey);
- using var services = serviceCollection.BuildServiceProvider();
- var userIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("");
- Console.WriteLine(userIdProvider.GetUserId());
- var envUserIdProvider = services.GetRequiredKeyedService<IUserIdProvider>("env");
- Console.WriteLine(envUserIdProvider.GetUserId());
输出结果如下:
codeduidaima.com
- (null)
- (null)
codeduidaima.com
- Console.WriteLine("userIdProvider == envUserIdProvider ?? {0}", userIdProvider == envUserIdProvider);
codeduidaima.com
- userIdProvider == envUserIdProvider ?? False
codeduidaima.com
- var nullUserIdProvider = services.GetRequiredKeyedService<IUserIdProvider>(null);
- Console.WriteLine(nullUserIdProvider.GetUserId());
codeduidaima.com
- System.InvalidOperationException: No service for type 'Net8Sample.<__Script>FE1DBF3BE6F8384813B223E3EAA03DBABDC4153F95C5B3EBB0E0807E84E7C20E4__IUserIdProvider' has been registered.
- at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetRequiredKeyedService(Type serviceType, Object serviceKey)
- at Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService[T](IServiceProvider provider, Object serviceKey)
另外如果我们注册 keyed service 的时候使用 null 作为 serviceKey,实际相当于注册了一个非 keyed service,比如说这两种注册方式是等价的
codeduidaima.com
- serviceCollection.AddKeyedSingleton<IUserIdProvider, NullUserIdProvider>(null);
- serviceCollection.AddSingleton<IUserIdProvider, NullUserIdProvider>();
ServiceKey in constructor
在构造方法中可以使用 ServiceKeyAttribute 来在构造方法中获取注册的 serviceKey,我们来看一个示例:
codeduidaima.com
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddKeyedTransient<MyNamedService>(KeyedService.AnyKey);
- using var services = serviceCollection.BuildServiceProvider();
- Console.WriteLine(services.GetRequiredKeyedService<MyNamedService>("Foo").Name);
- Console.WriteLine(services.GetRequiredKeyedService<MyNamedService>("Hello").Name);
- file sealed class MyNamedService
- {
- public MyNamedService([ServiceKey]string name)
- {
- Name = name;
- }
- public string Name { get; }
- }
codeduidaima.com
- Foo
- Hello
这里需要注意的是我们需要保证 constructor 中的 serviceKey 类型和获取服务时的类型应该是一致的,否则会有异常,比如:
codeduidaima.com
- Console.WriteLine(services.GetRequiredKeyedService<MyNamedService>(123).Name);
codeduidaima.com
- System.InvalidOperationException: The type of the key used for lookup doesn't match the type in the constructor parameter with the ServiceKey attribute.
- at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(ServiceIdentifier serviceIdentifier, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
- at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, ServiceIdentifier serviceIdentifier, Type implementationType, CallSiteChain callSiteChain)
- at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, Int32 slot)
- at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
- at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
- at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
- at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
- at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
- at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
- at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetKeyedService(Type serviceType, Object serviceKey)
- at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetRequiredKeyedService(Type serviceType, Object serviceKey)
- at Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService[T](IServiceProvider provider, Object serviceKey)
codeduidaima.com
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddKeyedTransient<MyKeyedService>(KeyedService.AnyKey);
- using var services = serviceCollection.BuildServiceProvider();
- Console.WriteLine(services.GetRequiredKeyedService<MyKeyedService>(new Category()
- {
- Id = 1,
- Name = "test"
- }).Name);
Scoped Sevice
目前对于 scoped service 的支持是有些问题的,使用 scoped service 使用会发生异常
codeduidaima.com
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddKeyedScoped<IUserIdProvider, NullUserIdProvider>("");
- using var services = serviceCollection.BuildServiceProvider();
- using var scope = services.CreateScope();
- var newId = scope.ServiceProvider.GetRequiredKeyedService<IIdGenerator>("").NewId();
- Console.WriteLine(newId);
codeduidaima.com
- System.InvalidOperationException: This service provider doesn't support keyed services.
- at Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService(IServiceProvider provider, Type serviceType, Object serviceKey)
- at Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService[T](IServiceProvider provider, Object serviceKey)
- at Net8Sample.KeyedServiceSample.ScopedSample()
感兴趣的可以尝试一下下面的示例,看看两个 API 的 response:
codeduidaima.com
- var builder = WebApplication.CreateBuilder();
- builder.Services.AddKeyedSingleton<IIdGenerator, GuidIdGenerator>("guid");
- var app = builder.Build();
- app.Map("/id0", ([FromKeyedServices("guid")]IIdGenerator idGenerator)
- => Result.Success<string>(idGenerator.NewId()));
- app.Map("/id", (HttpContext httpContext) =>
- {
- var idGenerator = httpContext.RequestServices.GetRequiredKeyedService<IIdGenerator>("guid");
- return Result.Success<string>(idGenerator.NewId());
- });
- await app.RunAsync();
其它
我们也可以结合 Options 来方便的实现基于 options 的 named service,示例如下:
codeduidaima.com
- var serviceCollection = new ServiceCollection();
- serviceCollection.Configure<TotpOptions>(x =>
- {
- x.Salt = "1234";
- });
- serviceCollection.AddKeyedTransient<ITotpService, TotpService>(KeyedService.AnyKey,
- (sp, key)=>
- new TotpService(sp.GetRequiredService<IOptionsMonitor<TotpOptions>>()
- .Get(key is string name ? name : Options.DefaultName)));
- using var services = serviceCollection.BuildServiceProvider(https://www.523it.com/);
- var totpService = services.GetRequiredKeyedService<ITotpService>(string.Empty);
- Console.WriteLine("Totp1: {0}", totpService.GetCode("Test1234"));
- var totpService2 = services.GetRequiredKeyedService<ITotpService>("test");
- Console.WriteLine("Totp2: {0}", totpService2.GetCode("Test1234"));
codeduidaima.com
- Totp1: 356934
- Totp2: 626994
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?