Provider 目录与解析器是处理多厂商场景的核心设计。它让消费方按稳定 Provider ID 路由到具体实现,跨 net5/net8/net10 使用同一公共 API。
核心概念
IntegrationProviderDescriptor
每个 Provider 的可执行能力描述,是事实源:
internal static class AlipayProviderDescriptor{ public static IntegrationProviderDescriptor Instance { get; } = new( providerId: "Alipay", displayName: "支付宝", domain: "Payment", capabilityTypes: new[] { typeof(IPaymentProvider) }, stability: IntegrationProviderStability.Preview, vendor: "Ant Group", apiVersion: "1.0", authenticationType: "RSA2", regions: new[] { "CN" }, limitations: new[] { "不支持银行卡直连" });}关键属性:
| 属性 | 说明 |
|---|---|
ProviderId | 稳定、大小写不敏感的标识,发布后不可修改 |
ProviderKey | 跨领域唯一键 {Domain}:{ProviderId} |
CapabilityTypes | 已实现并会注册到 resolver 的能力类型 |
Stability | Experimental / Preview / Stable / Deprecated |
Regions / Environments | 适用地区与环境 |
Limitations | 已知限制 |
Supports<TCapability>() 方法判断描述是否声明指定能力:
if (!descriptor.Supports<IPaymentProvider>()) throw new ArgumentException("Descriptor does not declare this capability.");IIntegrationProviderCatalog
枚举当前容器中所有已配置 Provider:
public interface IIntegrationProviderCatalog{ IReadOnlyCollection<IntegrationProviderDescriptor> Providers { get; } bool TryGet(string providerKey, out IntegrationProviderDescriptor descriptor); bool TryGet(string domain, string providerId, out IntegrationProviderDescriptor descriptor); IntegrationProviderDescriptor GetRequired(string providerKey);}IIntegrationProviderResolver<T>
按大小写不敏感的稳定 Provider ID 解析能力:
public interface IIntegrationProviderResolver<TCapability> where TCapability : class{ IReadOnlyCollection<TCapability> Providers { get; } bool TryResolve(string providerId, out TCapability capability); TCapability GetRequired(string providerId);}注册流程
// ① 厂商包注册时提交 Descriptor + 实现services.AddIntegrationProviderCapability<IPaymentProvider, AlipayPaymentProvider>( AlipayProviderDescriptor.Instance, serviceProvider => new AlipayPaymentProvider( serviceProvider.GetRequiredService<AlipayHttpClient>(), serviceProvider.GetRequiredService<IAlipayConfigProvider>(), serviceProvider.GetRequiredService<ILogger<AlipayPaymentProvider>>()));注册内部完成四件事:
- 提交
IntegrationProviderDescriptor到目录(按ProviderKey去重) TryAdd实现类型- 添加
IntegrationProviderRegistration<T>单例记录 - 在 net8+ 上额外注册 keyed service(按
ProviderId作为 key)
消费模式
单 Provider 兼容(直接注入)
只配置一个 Provider 时,仍可直接注入能力接口:
public class CheckoutService(IPaymentProvider payment){ public async Task<PaymentResult<PaymentOrderResult>> PayAsync(PaymentOrderRequest req) => await payment.CreateOrderAsync(req);}这是兼容别名(registerLegacyAlias = true),用 TryAdd 注册,不覆盖已注册的生产实现。
多 Provider 路由
配置多家厂商时,必须使用 resolver:
public sealed class PaymentRouter( IIntegrationProviderResolver<IPaymentProvider> providers){ public IPaymentProvider Resolve(string providerId) => providers.GetRequired(providerId);}Provider ID 是持久配置值,发布后不得因显示名称或类名变化而修改。
跨 TFM 设计
resolver 使用 scoped 字典 Adapter,不依赖 .NET 8 keyed services:
// net8+ 额外注册 keyed service 作为可选适配器#if NET8_0_OR_GREATERservices.Add(new ServiceDescriptor( typeof(TCapability), descriptor.ProviderId, // 以 ProviderId 作为 key (serviceProvider, _) => serviceProvider.GetRequiredService<TImplementation>(), implementationLifetime));#endifnet5.0 没有 keyed services,但跨 TFM resolver 仍然完全工作。net8+ host 可以选择使用 keyed DI,但 canonical API 是 resolver。
.All 包的验证要求
聚合包必须有测试证明每个配置 Provider 可枚举、可按 ID 解析:
[Fact]public void Payment_All_Registers_All_Configured_Providers(){ var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string?> { ["Payment:Alipay:AppId"] = "test", ["Payment:WeChatPay:AppId"] = "test", }) .Build();
var services = new ServiceCollection(); services.AddBitzsoftPaymentAll(config); var sp = services.BuildServiceProvider(validateScopes: true);
var resolver = sp.GetRequiredService<IIntegrationProviderResolver<IPaymentProvider>>(); Assert.True(resolver.TryResolve("Alipay", out _)); Assert.True(resolver.TryResolve("WeChatPay", out _));}