keyed services需严格按规则注册、注入和解析:必须用addkeyed*方法注册,构造函数需[fromkeyedservices("key")]标注,手动解析用getrequiredkeyedservice(key),键名大小写敏感且类型须一致。

Keyed Services 不是“注册多个实现就能自动选中”的功能,漏掉注册、注入或解析任一环节,InvalidOperationException 或 null 就立刻出现。
注册必须用 AddKeyedScoped/AddKeyedSingleton/AddKeyedTransient
普通注册方法(如 AddScoped<iemailservice smtpemailservice>()</iemailservice>)对 Keyed Service 完全无效——容器不会把它们当作键控服务看待,也不会存入键控索引表。
- 错误写法:
builder.Services.AddScoped<iemailservice smtpemailservice>();</iemailservice>+builder.Services.AddScoped<iemailservice sendgridemailservice>();</iemailservice>→ 后者覆盖前者,且无法按键获取 - 正确写法:
builder.Services.AddKeyedScoped<iemailservice smtpemailservice>("smtp");</iemailservice>和builder.Services.AddKeyedScoped<iemailservice sendgridemailservice>("sendgrid");</iemailservice> - 键支持
string、enum、int等可比较类型,但不支持null;若需兜底逻辑,显式注册一个如"default"键的服务
构造函数注入必须加 [FromKeyedServices("xxx")]
即使注册了两个键控实现,直接在构造函数里写 IEmailService service 仍会失败:容器无法推断你要哪个键,最终注入 null 或抛出异常。
- 必须显式标注:
public class NotificationHandler([FromKeyedServices("smtp")] IEmailService emailService) - 键名大小写敏感,
"SMTP"和"smtp"被视为不同键 - 若依赖链中某环节也需键控服务(比如
SmtpEmailService构造时需要[FromKeyedServices("smtp-config")] IConfigRepository),每一层都得加特性,不能只在顶层加
手动解析要用 GetRequiredKeyedService<t>(key)</t>
运行时动态选型(比如根据用户配置决定发短信还是邮件)不能靠构造函数注入,得用服务提供器手动拉取。
- 错误写法:
provider.GetService<iemailservice>()</iemailservice>→ 返回null(非键控方式查不到) - 正确写法:
provider.GetRequiredKeyedService<iemailservice>(user.NotificationType.ToLower())</iemailservice>→ 找不到键时直接抛InvalidOperationException,比静默null更早暴露问题 - 可用
TryGetKeyedService<t>(key, out T instance)</t>做安全尝试,避免异常 -
KeyedService.AnyKey是个特殊兜底机制:注册时用它,后续任何未注册的键都会命中该实例(注意:不是泛匹配,而是“未命中时 fallback”)
Blazor 和 MVC 中的注入写法差异
Blazor 组件支持 [Inject(Key = "xxx")],但 Razor Pages / MVC 的 @inject 指令目前不支持键控注入——这是容易忽略的兼容性断点。
- Blazor 组件中:
[Inject(Key = "emailSev")] public INotifyService EmailService { get; set; } - MVC Controller 或 Minimal API 中,只能走构造函数注入或
HttpContext.RequestServices手动解析 - Minimal API 的 endpoint handler 支持
[FromKeyedServices("xxx")]参数,但必须放在参数列表最前,且不能和普通 DI 参数混用(例如不能同时有[FromKeyedServices("a")] ICache a和IConfiguration config,后者需显式从HttpContext拿)
最常被跳过的其实是键类型一致性:注册用 ServiceInstanceEnum.Smtp,注入却传字符串 "smtp",或注册用小写而代码里拼成大写,这种错不会编译报红,只会在运行时炸。











