diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
index 5c79175a..4530e537 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
@@ -448,6 +448,126 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Assert.That(results, Is.EqualTo([3, 4]));
}
+ ///
+ /// 验证当 generated request invoker provider 的 descriptor 枚举抛出异常时,
+ /// registrar 会跳过 generated descriptor 预热并回退到反射路径。
+ ///
+ [Test]
+ public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Request_Descriptor_Enumeration_Throws()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(ThrowingEnumeratingRequestInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.ThrowingEnumeratingRequestInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
+ Assert.That(response, Is.EqualTo("runtime:payload"));
+ }
+
+ ///
+ /// 验证当 generated stream invoker provider 的 descriptor 枚举抛出异常时,
+ /// registrar 会跳过 generated descriptor 预热并回退到反射建流路径。
+ ///
+ [Test]
+ public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Stream_Descriptor_Enumeration_Throws()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(ThrowingEnumeratingStreamInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.ThrowingEnumeratingStreamInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
+ Assert.That(results, Is.EqualTo([3, 4]));
+ }
+
+ ///
+ /// 验证当 request descriptor 枚举返回重复 request-response pair 时,
+ /// registrar 会稳定保留首个有效描述符,并忽略后续重复项。
+ ///
+ [Test]
+ public async Task SendAsync_Should_Use_First_Generated_Request_Descriptor_When_Duplicates_Are_Enumerated()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(DuplicateEnumeratingRequestInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.DuplicateEnumeratingRequestInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
+ Assert.That(response, Is.EqualTo("generated:payload"));
+ }
+
+ ///
+ /// 验证当 stream descriptor 枚举返回重复 request-response pair 时,
+ /// registrar 会稳定保留首个有效描述符,并忽略后续重复项。
+ ///
+ [Test]
+ public async Task CreateStream_Should_Use_First_Generated_Stream_Descriptor_When_Duplicates_Are_Enumerated()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(DuplicateEnumeratingStreamInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.DuplicateEnumeratingStreamInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
+ Assert.That(results, Is.EqualTo([30, 31]));
+ }
+
+ ///
+ /// 验证当 request descriptor 枚举项与 provider 的 TryGetDescriptor 结果不一致时,
+ /// registrar 会忽略该坏 descriptor,并继续回退到反射路径。
+ ///
+ [Test]
+ public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Enumerated_Request_Descriptor_Does_Not_Match_Provider()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(MismatchedEnumeratingRequestInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.MismatchedEnumeratingRequestInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
+ Assert.That(response, Is.EqualTo("runtime:payload"));
+ }
+
+ ///
+ /// 验证当 stream descriptor 枚举项与 provider 的 TryGetDescriptor 结果不一致时,
+ /// registrar 会忽略该坏 descriptor,并继续回退到反射建流路径。
+ ///
+ [Test]
+ public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Enumerated_Stream_Descriptor_Does_Not_Match_Provider()
+ {
+ var generatedAssembly = CreateGeneratedAssembly(
+ typeof(MismatchedEnumeratingStreamInvokerProviderRegistry),
+ "GFramework.Cqrs.Tests.Cqrs.MismatchedEnumeratingStreamInvokerAssembly, Version=1.0.0.0");
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
+ Assert.That(results, Is.EqualTo([3, 4]));
+ }
+
///
/// 模拟返回实例 request invoker 方法的 generated registry。
///
@@ -860,6 +980,382 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
}
}
+ ///
+ /// 模拟 descriptor 枚举阶段抛出异常的 request invoker provider。
+ ///
+ private sealed class ThrowingEnumeratingRequestInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsRequestInvokerProvider,
+ IEnumeratesCqrsRequestInvokerDescriptors
+ {
+ private static readonly CqrsRequestInvokerDescriptor Descriptor = new(
+ typeof(IRequestHandler),
+ typeof(GeneratedRequestInvokerProviderRegistry).GetMethod(
+ "InvokeGenerated",
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IRequestHandler),
+ typeof(GeneratedRequestInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsRequestInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ throw new InvalidOperationException("request descriptors failed");
+ }
+ }
+
+ ///
+ /// 模拟 descriptor 枚举阶段抛出异常的 stream invoker provider。
+ ///
+ private sealed class ThrowingEnumeratingStreamInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsStreamInvokerProvider,
+ IEnumeratesCqrsStreamInvokerDescriptors
+ {
+ private static readonly CqrsStreamInvokerDescriptor Descriptor = new(
+ typeof(IStreamRequestHandler),
+ typeof(GeneratedStreamInvokerProviderRegistry).GetMethod(
+ "InvokeGenerated",
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IStreamRequestHandler),
+ typeof(GeneratedStreamInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsStreamInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ throw new InvalidOperationException("stream descriptors failed");
+ }
+ }
+
+ ///
+ /// 模拟返回重复 request descriptor 条目的 generated registry。
+ ///
+ private sealed class DuplicateEnumeratingRequestInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsRequestInvokerProvider,
+ IEnumeratesCqrsRequestInvokerDescriptors
+ {
+ private static readonly CqrsRequestInvokerDescriptor PrimaryDescriptor = new(
+ typeof(IRequestHandler),
+ typeof(GeneratedRequestInvokerProviderRegistry).GetMethod(
+ "InvokeGenerated",
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ private static readonly CqrsRequestInvokerDescriptor SecondaryDescriptor = new(
+ typeof(IRequestHandler),
+ typeof(DuplicateEnumeratingRequestInvokerProviderRegistry).GetMethod(
+ nameof(InvokeAlternativeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IRequestHandler),
+ typeof(GeneratedRequestInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsRequestInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
+ {
+ descriptor = PrimaryDescriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return
+ [
+ new CqrsRequestInvokerDescriptorEntry(typeof(GeneratedRequestInvokerRequest), typeof(string), PrimaryDescriptor),
+ new CqrsRequestInvokerDescriptorEntry(typeof(GeneratedRequestInvokerRequest), typeof(string), SecondaryDescriptor)
+ ];
+ }
+
+ private static ValueTask InvokeAlternativeGenerated(
+ object handler,
+ object request,
+ CancellationToken cancellationToken)
+ {
+ return ValueTask.FromResult("duplicate:payload");
+ }
+ }
+
+ ///
+ /// 模拟返回重复 stream descriptor 条目的 generated registry。
+ ///
+ private sealed class DuplicateEnumeratingStreamInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsStreamInvokerProvider,
+ IEnumeratesCqrsStreamInvokerDescriptors
+ {
+ private static readonly CqrsStreamInvokerDescriptor PrimaryDescriptor = new(
+ typeof(IStreamRequestHandler),
+ typeof(GeneratedStreamInvokerProviderRegistry).GetMethod(
+ "InvokeGenerated",
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ private static readonly CqrsStreamInvokerDescriptor SecondaryDescriptor = new(
+ typeof(IStreamRequestHandler),
+ typeof(DuplicateEnumeratingStreamInvokerProviderRegistry).GetMethod(
+ nameof(InvokeAlternativeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IStreamRequestHandler),
+ typeof(GeneratedStreamInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsStreamInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
+ {
+ descriptor = PrimaryDescriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return
+ [
+ new CqrsStreamInvokerDescriptorEntry(typeof(GeneratedStreamInvokerRequest), typeof(int), PrimaryDescriptor),
+ new CqrsStreamInvokerDescriptorEntry(typeof(GeneratedStreamInvokerRequest), typeof(int), SecondaryDescriptor)
+ ];
+ }
+
+ private static object InvokeAlternativeGenerated(object handler, object request, CancellationToken cancellationToken)
+ {
+ return new[] { 900, 901 }.ToAsyncEnumerable();
+ }
+ }
+
+ ///
+ /// 模拟枚举出的 request descriptor 与 provider 显式查询结果不一致的 generated registry。
+ ///
+ private sealed class MismatchedEnumeratingRequestInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsRequestInvokerProvider,
+ IEnumeratesCqrsRequestInvokerDescriptors
+ {
+ private static readonly CqrsRequestInvokerDescriptor ProviderDescriptor = new(
+ typeof(IRequestHandler),
+ typeof(GeneratedRequestInvokerProviderRegistry).GetMethod(
+ "InvokeGenerated",
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ private static readonly CqrsRequestInvokerDescriptor EnumeratedDescriptor = new(
+ typeof(IRequestHandler),
+ typeof(MismatchedEnumeratingRequestInvokerProviderRegistry).GetMethod(
+ nameof(InvokeAlternativeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IRequestHandler),
+ typeof(GeneratedRequestInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsRequestInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
+ {
+ descriptor = ProviderDescriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return
+ [
+ new CqrsRequestInvokerDescriptorEntry(
+ typeof(GeneratedRequestInvokerRequest),
+ typeof(string),
+ EnumeratedDescriptor)
+ ];
+ }
+
+ private static ValueTask InvokeAlternativeGenerated(
+ object handler,
+ object request,
+ CancellationToken cancellationToken)
+ {
+ return ValueTask.FromResult("mismatched:payload");
+ }
+ }
+
+ ///
+ /// 模拟枚举出的 stream descriptor 与 provider 显式查询结果不一致的 generated registry。
+ ///
+ private sealed class MismatchedEnumeratingStreamInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsStreamInvokerProvider,
+ IEnumeratesCqrsStreamInvokerDescriptors
+ {
+ private static readonly CqrsStreamInvokerDescriptor ProviderDescriptor = new(
+ typeof(IStreamRequestHandler),
+ typeof(GeneratedStreamInvokerProviderRegistry).GetMethod(
+ "InvokeGenerated",
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ private static readonly CqrsStreamInvokerDescriptor EnumeratedDescriptor = new(
+ typeof(IStreamRequestHandler),
+ typeof(MismatchedEnumeratingStreamInvokerProviderRegistry).GetMethod(
+ nameof(InvokeAlternativeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ ///
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(IStreamRequestHandler),
+ typeof(GeneratedStreamInvokerRequestHandler));
+ }
+
+ ///
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsStreamInvokerDescriptor? descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(requestType);
+ ArgumentNullException.ThrowIfNull(responseType);
+
+ if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
+ {
+ descriptor = ProviderDescriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ public IReadOnlyList GetDescriptors()
+ {
+ return
+ [
+ new CqrsStreamInvokerDescriptorEntry(
+ typeof(GeneratedStreamInvokerRequest),
+ typeof(int),
+ EnumeratedDescriptor)
+ ];
+ }
+
+ private static object InvokeAlternativeGenerated(object handler, object request, CancellationToken cancellationToken)
+ {
+ return new[] { 700, 701 }.ToAsyncEnumerable();
+ }
+ }
+
///
/// 创建带有 generated request invoker registry 元数据的程序集替身。
///
diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
index a332927c..1b90edb8 100644
--- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
+++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
@@ -16,6 +16,13 @@ namespace GFramework.Cqrs.Internal;
///
internal static class CqrsHandlerRegistrar
{
+ ///
+ /// 描述 generated invoker descriptor 在 registrar 预热阶段使用的 request/response 类型对键。
+ ///
+ /// 请求运行时类型。
+ /// 响应运行时类型。
+ private readonly record struct InvokerDescriptorKey(Type RequestType, Type ResponseType);
+
// 卸载安全的进程级缓存:程序集元数据只按弱键复用。
// 若程序集来自 collectible AssemblyLoadContext,被回收后会重新分析,而不会被静态缓存永久钉住。
private static readonly WeakKeyCache AssemblyMetadataCache =
@@ -321,8 +328,48 @@ internal static class CqrsHandlerRegistrar
if (provider is not IEnumeratesCqrsRequestInvokerDescriptors descriptorSource)
return;
- foreach (var descriptorEntry in descriptorSource.GetDescriptors())
+ IReadOnlyList? descriptors;
+ try
{
+ descriptors = descriptorSource.GetDescriptors();
+ }
+ catch (Exception exception)
+ {
+ logger.Warn(
+ $"Failed to enumerate generated CQRS request invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName}. Falling back to runtime reflection for request invokers: {exception.Message}");
+ return;
+ }
+
+ if (descriptors is null)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS request invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName} because GetDescriptors() returned null.");
+ return;
+ }
+
+ var registeredKeys = new HashSet();
+ foreach (var descriptorEntry in descriptors)
+ {
+ if (descriptorEntry is null)
+ {
+ logger.Warn(
+ $"Ignoring null generated CQRS request invoker descriptor entry from provider {provider.GetType().FullName} in assembly {assemblyName}.");
+ continue;
+ }
+
+ var descriptorKey = new InvokerDescriptorKey(
+ descriptorEntry.RequestType,
+ descriptorEntry.ResponseType);
+ if (!registeredKeys.Add(descriptorKey))
+ {
+ logger.Warn(
+ $"Ignoring duplicate generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName}.");
+ continue;
+ }
+
+ if (!TryValidateEnumeratedRequestInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
+ continue;
+
CqrsDispatcher.RegisterGeneratedRequestInvokerDescriptor(
descriptorEntry.RequestType,
descriptorEntry.ResponseType,
@@ -376,8 +423,48 @@ internal static class CqrsHandlerRegistrar
if (provider is not IEnumeratesCqrsStreamInvokerDescriptors descriptorSource)
return;
- foreach (var descriptorEntry in descriptorSource.GetDescriptors())
+ IReadOnlyList? descriptors;
+ try
{
+ descriptors = descriptorSource.GetDescriptors();
+ }
+ catch (Exception exception)
+ {
+ logger.Warn(
+ $"Failed to enumerate generated CQRS stream invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName}. Falling back to runtime reflection for stream invokers: {exception.Message}");
+ return;
+ }
+
+ if (descriptors is null)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS stream invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName} because GetDescriptors() returned null.");
+ return;
+ }
+
+ var registeredKeys = new HashSet();
+ foreach (var descriptorEntry in descriptors)
+ {
+ if (descriptorEntry is null)
+ {
+ logger.Warn(
+ $"Ignoring null generated CQRS stream invoker descriptor entry from provider {provider.GetType().FullName} in assembly {assemblyName}.");
+ continue;
+ }
+
+ var descriptorKey = new InvokerDescriptorKey(
+ descriptorEntry.RequestType,
+ descriptorEntry.ResponseType);
+ if (!registeredKeys.Add(descriptorKey))
+ {
+ logger.Warn(
+ $"Ignoring duplicate generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName}.");
+ continue;
+ }
+
+ if (!TryValidateEnumeratedStreamInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
+ continue;
+
CqrsDispatcher.RegisterGeneratedStreamInvokerDescriptor(
descriptorEntry.RequestType,
descriptorEntry.ResponseType,
@@ -387,6 +474,96 @@ internal static class CqrsHandlerRegistrar
}
}
+ ///
+ /// 校验 request descriptor 枚举项是否与 provider 的显式查询结果保持一致。
+ ///
+ /// 当前正在预热的 request invoker provider。
+ /// 当前枚举到的描述符条目。
+ /// 当前程序集的稳定名称。
+ /// 日志记录器。
+ /// 当该枚举项可安全写入 dispatcher 缓存时返回 ;否则返回 。
+ private static bool TryValidateEnumeratedRequestInvokerDescriptor(
+ ICqrsRequestInvokerProvider provider,
+ CqrsRequestInvokerDescriptorEntry descriptorEntry,
+ string assemblyName,
+ ILogger logger)
+ {
+ try
+ {
+ if (!provider.TryGetDescriptor(
+ descriptorEntry.RequestType,
+ descriptorEntry.ResponseType,
+ out var resolvedDescriptor) ||
+ resolvedDescriptor is null)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor did not return a matching descriptor.");
+ return false;
+ }
+
+ if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) ||
+ resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because the enumerated descriptor does not match TryGetDescriptor.");
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception exception)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor threw: {exception.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// 校验 stream descriptor 枚举项是否与 provider 的显式查询结果保持一致。
+ ///
+ /// 当前正在预热的 stream invoker provider。
+ /// 当前枚举到的描述符条目。
+ /// 当前程序集的稳定名称。
+ /// 日志记录器。
+ /// 当该枚举项可安全写入 dispatcher 缓存时返回 ;否则返回 。
+ private static bool TryValidateEnumeratedStreamInvokerDescriptor(
+ ICqrsStreamInvokerProvider provider,
+ CqrsStreamInvokerDescriptorEntry descriptorEntry,
+ string assemblyName,
+ ILogger logger)
+ {
+ try
+ {
+ if (!provider.TryGetDescriptor(
+ descriptorEntry.RequestType,
+ descriptorEntry.ResponseType,
+ out var resolvedDescriptor) ||
+ resolvedDescriptor is null)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor did not return a matching descriptor.");
+ return false;
+ }
+
+ if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) ||
+ resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because the enumerated descriptor does not match TryGetDescriptor.");
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception exception)
+ {
+ logger.Warn(
+ $"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor threw: {exception.Message}");
+ return false;
+ }
+ }
+
///
/// 将 generated registry 的 fallback 元数据转换为统一的注册结果,并记录下一阶段是定向补扫还是整程序集扫描。
///