feat(cqrs): 添加 CQRS 处理器注册器和源码生成器

- 实现 CqrsHandlerRegistrar 类用于扫描并注册 CQRS 处理器
- 添加源码生成器自动生成 CQRS 处理器注册器减少反射开销
- 实现运行时回退机制在生成注册器不可用时使用反射扫描
- 添加完整的单元测试验证处理器注册顺序和容错行为
- 支持请求、通知和流式处理器的自动注册功能
- 实现稳定的处理器注册顺序保证跨环境一致性
- 添加详细的诊断日志记录注册过程和异常情况
This commit is contained in:
GeWuYou 2026-04-16 08:49:13 +08:00
parent a7604de804
commit bc9336428e
5 changed files with 328 additions and 35 deletions

View File

@ -13,6 +13,9 @@ namespace GFramework.Cqrs.Tests.Cqrs;
[TestFixture] [TestFixture]
internal sealed class CqrsHandlerRegistrarTests internal sealed class CqrsHandlerRegistrarTests
{ {
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
/// <summary> /// <summary>
/// 初始化测试容器并重置共享状态。 /// 初始化测试容器并重置共享状态。
/// </summary> /// </summary>
@ -42,9 +45,6 @@ internal sealed class CqrsHandlerRegistrarTests
DeterministicNotificationHandlerState.Reset(); DeterministicNotificationHandlerState.Reset();
} }
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
/// <summary> /// <summary>
/// 验证自动扫描到的通知处理器会按稳定名称顺序执行,而不是依赖反射枚举顺序。 /// 验证自动扫描到的通知处理器会按稳定名称顺序执行,而不是依赖反射枚举顺序。
/// </summary> /// </summary>
@ -188,6 +188,50 @@ internal sealed class CqrsHandlerRegistrarTests
LoggerFactoryResolver.Provider = originalProvider; LoggerFactoryResolver.Provider = originalProvider;
} }
} }
/// <summary>
/// 验证当生成注册器显式要求 reflection fallback 时,运行时会补扫剩余 handlers
/// 同时避免把已由生成注册器注册的映射重复写入服务集合。
/// </summary>
[Test]
public void RegisterHandlers_Should_Combine_Generated_Registry_With_Reflection_Fallback_Without_Duplicates()
{
var generatedAssembly = new Mock<Assembly>();
generatedAssembly
.SetupGet(static assembly => assembly.FullName)
.Returns("GFramework.Core.Tests.Cqrs.PartialGeneratedRegistryAssembly, Version=1.0.0.0");
generatedAssembly
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
.Returns([new CqrsHandlerRegistryAttribute(typeof(PartialGeneratedNotificationHandlerRegistry))]);
generatedAssembly
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false))
.Returns([new CqrsReflectionFallbackAttribute()]);
generatedAssembly
.Setup(static assembly => assembly.GetTypes())
.Returns(
[
typeof(GeneratedRegistryNotificationHandler),
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
]);
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
var registrations = container.GetServicesUnsafe
.Where(static descriptor =>
descriptor.ServiceType == typeof(INotificationHandler<GeneratedRegistryNotification>) &&
descriptor.ImplementationType is not null)
.Select(static descriptor => descriptor.ImplementationType!)
.ToList();
Assert.That(
registrations,
Is.EqualTo(
[
typeof(GeneratedRegistryNotificationHandler),
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
]));
}
} }
/// <summary> /// <summary>
@ -337,3 +381,52 @@ internal sealed class GeneratedNotificationHandlerRegistry : ICqrsHandlerRegistr
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}."); $"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
} }
} }
/// <summary>
/// 用于验证“生成注册器 + reflection fallback”组合路径的私有嵌套处理器容器。
/// </summary>
internal sealed class ReflectionFallbackNotificationContainer
{
/// <summary>
/// 获取仅能通过反射补扫接入的私有嵌套处理器类型。
/// </summary>
public static Type ReflectionOnlyHandlerType => typeof(ReflectionOnlyGeneratedRegistryNotificationHandler);
private sealed class ReflectionOnlyGeneratedRegistryNotificationHandler
: INotificationHandler<GeneratedRegistryNotification>
{
/// <summary>
/// 处理测试通知。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(GeneratedRegistryNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
}
}
/// <summary>
/// 模拟局部生成注册器场景中,仅注册“可由生成代码直接引用”的那部分 handlers。
/// </summary>
internal sealed class PartialGeneratedNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 将生成路径可见的通知处理器注册到目标服务集合。
/// </summary>
/// <param name="services">承载处理器映射的服务集合。</param>
/// <param name="logger">用于记录注册诊断的日志器。</param>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(INotificationHandler<GeneratedRegistryNotification>),
typeof(GeneratedRegistryNotificationHandler));
logger.Debug(
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
}
}

View File

@ -0,0 +1,14 @@
namespace GFramework.Cqrs;
/// <summary>
/// 标记程序集中的 CQRS 生成注册器仍需要运行时补充反射扫描。
/// </summary>
/// <remarks>
/// 该特性通常由源码生成器自动添加到消费端程序集。
/// 当生成器只能安全生成部分 handler 映射时,运行时会先执行生成注册器,再补一次带去重的反射扫描,
/// 以覆盖那些生成代码无法直接引用的 handler 类型。
/// </remarks>
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class CqrsReflectionFallbackAttribute : Attribute
{
}

View File

@ -1,4 +1,3 @@
using System.Reflection;
using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Logging;
using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs;
@ -32,7 +31,9 @@ internal static class CqrsHandlerRegistrar
.Distinct() .Distinct()
.OrderBy(GetAssemblySortKey, StringComparer.Ordinal)) .OrderBy(GetAssemblySortKey, StringComparer.Ordinal))
{ {
if (TryRegisterGeneratedHandlers(container.GetServicesUnsafe, assembly, logger)) var generatedRegistrationResult =
TryRegisterGeneratedHandlers(container.GetServicesUnsafe, assembly, logger);
if (generatedRegistrationResult == GeneratedRegistrationResult.FullyHandled)
continue; continue;
RegisterAssemblyHandlers(container.GetServicesUnsafe, assembly, logger); RegisterAssemblyHandlers(container.GetServicesUnsafe, assembly, logger);
@ -45,8 +46,11 @@ internal static class CqrsHandlerRegistrar
/// <param name="services">目标服务集合。</param> /// <param name="services">目标服务集合。</param>
/// <param name="assembly">当前要处理的程序集。</param> /// <param name="assembly">当前要处理的程序集。</param>
/// <param name="logger">日志记录器。</param> /// <param name="logger">日志记录器。</param>
/// <returns>当成功使用生成注册器时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns> /// <returns>生成注册器的使用结果。</returns>
private static bool TryRegisterGeneratedHandlers(IServiceCollection services, Assembly assembly, ILogger logger) private static GeneratedRegistrationResult TryRegisterGeneratedHandlers(
IServiceCollection services,
Assembly assembly,
ILogger logger)
{ {
var assemblyName = GetAssemblySortKey(assembly); var assemblyName = GetAssemblySortKey(assembly);
@ -62,7 +66,7 @@ internal static class CqrsHandlerRegistrar
.ToList(); .ToList();
if (registryTypes.Count == 0) if (registryTypes.Count == 0)
return false; return GeneratedRegistrationResult.NoGeneratedRegistry;
var registries = new List<ICqrsHandlerRegistry>(registryTypes.Count); var registries = new List<ICqrsHandlerRegistry>(registryTypes.Count);
foreach (var registryType in registryTypes) foreach (var registryType in registryTypes)
@ -71,21 +75,21 @@ internal static class CqrsHandlerRegistrar
{ {
logger.Warn( logger.Warn(
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it does not implement {typeof(ICqrsHandlerRegistry).FullName}."); $"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it does not implement {typeof(ICqrsHandlerRegistry).FullName}.");
return false; return GeneratedRegistrationResult.NoGeneratedRegistry;
} }
if (registryType.IsAbstract) if (registryType.IsAbstract)
{ {
logger.Warn( logger.Warn(
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it is abstract."); $"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it is abstract.");
return false; return GeneratedRegistrationResult.NoGeneratedRegistry;
} }
if (Activator.CreateInstance(registryType, nonPublic: true) is not ICqrsHandlerRegistry registry) if (Activator.CreateInstance(registryType, nonPublic: true) is not ICqrsHandlerRegistry registry)
{ {
logger.Warn( logger.Warn(
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it could not be instantiated."); $"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it could not be instantiated.");
return false; return GeneratedRegistrationResult.NoGeneratedRegistry;
} }
registries.Add(registry); registries.Add(registry);
@ -98,7 +102,14 @@ internal static class CqrsHandlerRegistrar
registry.Register(services, logger); registry.Register(services, logger);
} }
return true; if (RequiresReflectionFallback(assembly))
{
logger.Debug(
$"Generated CQRS registry for assembly {assemblyName} requested reflection fallback for unsupported handlers.");
return GeneratedRegistrationResult.RequiresReflectionFallback;
}
return GeneratedRegistrationResult.FullyHandled;
} }
catch (Exception exception) catch (Exception exception)
{ {
@ -106,7 +117,7 @@ internal static class CqrsHandlerRegistrar
$"Generated CQRS handler registry discovery failed for assembly {assemblyName}. Falling back to reflection scan."); $"Generated CQRS handler registry discovery failed for assembly {assemblyName}. Falling back to reflection scan.");
logger.Warn( logger.Warn(
$"Failed to use generated CQRS handler registry for assembly {assemblyName}: {exception.Message}"); $"Failed to use generated CQRS handler registry for assembly {assemblyName}: {exception.Message}");
return false; return GeneratedRegistrationResult.NoGeneratedRegistry;
} }
} }
@ -128,6 +139,13 @@ internal static class CqrsHandlerRegistrar
foreach (var handlerInterface in handlerInterfaces) foreach (var handlerInterface in handlerInterfaces)
{ {
if (IsHandlerMappingAlreadyRegistered(services, handlerInterface, implementationType))
{
logger.Debug(
$"Skipping duplicate CQRS handler {implementationType.FullName} as {handlerInterface.FullName}.");
continue;
}
// Request/notification handlers receive context injection before every dispatch. // Request/notification handlers receive context injection before every dispatch.
// Transient registration avoids sharing mutable Context across concurrent requests. // Transient registration avoids sharing mutable Context across concurrent requests.
services.AddTransient(handlerInterface, implementationType); services.AddTransient(handlerInterface, implementationType);
@ -202,6 +220,27 @@ internal static class CqrsHandlerRegistrar
definition == typeof(IStreamRequestHandler<,>); definition == typeof(IStreamRequestHandler<,>);
} }
/// <summary>
/// 判断生成注册器是否要求运行时继续补充反射扫描。
/// </summary>
private static bool RequiresReflectionFallback(Assembly assembly)
{
return assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), inherit: false)?.Length > 0;
}
/// <summary>
/// 判断同一 handler 映射是否已经由生成注册器或先前扫描步骤写入服务集合。
/// </summary>
private static bool IsHandlerMappingAlreadyRegistered(
IServiceCollection services,
Type handlerInterface,
Type implementationType)
{
return services.Any(descriptor =>
descriptor.ServiceType == handlerInterface &&
descriptor.ImplementationType == implementationType);
}
/// <summary> /// <summary>
/// 生成程序集排序键,保证跨运行环境的处理器注册顺序稳定。 /// 生成程序集排序键,保证跨运行环境的处理器注册顺序稳定。
/// </summary> /// </summary>
@ -217,4 +256,11 @@ internal static class CqrsHandlerRegistrar
{ {
return type.FullName ?? type.Name; return type.FullName ?? type.Name;
} }
private enum GeneratedRegistrationResult
{
NoGeneratedRegistry,
FullyHandled,
RequiresReflectionFallback
}
} }

View File

@ -61,6 +61,11 @@ public class CqrsHandlerRegistryGeneratorTests
{ {
public CqrsHandlerRegistryAttribute(Type registryType) { } public CqrsHandlerRegistryAttribute(Type registryType) { }
} }
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class CqrsReflectionFallbackAttribute : Attribute
{
}
} }
namespace TestApp namespace TestApp
@ -120,10 +125,120 @@ public class CqrsHandlerRegistryGeneratorTests
} }
/// <summary> /// <summary>
/// 验证当程序集包含生成代码无法合法引用的私有嵌套处理器时,生成器会放弃产出并让运行时回退到反射扫描。 /// 验证当程序集包含生成代码无法合法引用的私有嵌套处理器时,生成器仍会为可见 handlers 生成注册器,
/// 并额外标记运行时补充反射扫描。
/// </summary> /// </summary>
[Test] [Test]
public async Task Skips_Generation_When_Assembly_Contains_Private_Nested_Handler() public async Task
Generates_Visible_Handlers_And_Requests_Reflection_Fallback_When_Assembly_Contains_Private_Nested_Handler()
{
const string source = """
using System;
namespace Microsoft.Extensions.DependencyInjection
{
public interface IServiceCollection { }
public static class ServiceCollectionServiceExtensions
{
public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
}
}
namespace GFramework.Core.Abstractions.Logging
{
public interface ILogger
{
void Debug(string msg);
}
}
namespace GFramework.Cqrs.Abstractions.Cqrs
{
public interface IRequest<TResponse> { }
public interface INotification { }
public interface IStreamRequest<TResponse> { }
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
}
namespace GFramework.Cqrs
{
public interface ICqrsHandlerRegistry
{
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class CqrsHandlerRegistryAttribute : Attribute
{
public CqrsHandlerRegistryAttribute(Type registryType) { }
}
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class CqrsReflectionFallbackAttribute : Attribute
{
}
}
namespace TestApp
{
using GFramework.Cqrs.Abstractions.Cqrs;
public sealed record VisibleRequest() : IRequest<string>;
public sealed class Container
{
private sealed record HiddenRequest() : IRequest<string>;
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, string> { }
}
public sealed class VisibleHandler : IRequestHandler<VisibleRequest, string> { }
}
""";
const string expected = """
// <auto-generated />
#nullable enable
[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]
[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute()]
namespace GFramework.Generated.Cqrs;
internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry
{
public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger)
{
if (services is null)
throw new global::System.ArgumentNullException(nameof(services));
if (logger is null)
throw new global::System.ArgumentNullException(nameof(logger));
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
services,
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.VisibleRequest, string>),
typeof(global::TestApp.VisibleHandler));
logger.Debug("Registered CQRS handler TestApp.VisibleHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.VisibleRequest, string>.");
}
}
""";
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
source,
("CqrsHandlerRegistry.g.cs", expected));
}
/// <summary>
/// 验证当旧版 runtime 合同中不存在 reflection fallback 标记特性时,
/// 生成器会保留此前的整程序集回退行为,避免丢失不可见 handlers。
/// </summary>
[Test]
public async Task Skips_Generation_For_Unsupported_Handler_When_Fallback_Marker_Is_Unavailable()
{ {
const string source = """ const string source = """
using System; using System;

View File

@ -16,6 +16,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
private const string IStreamRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IStreamRequestHandler`2"; private const string IStreamRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IStreamRequestHandler`2";
private const string ICqrsHandlerRegistryMetadataName = $"{CqrsRuntimeNamespace}.ICqrsHandlerRegistry"; private const string ICqrsHandlerRegistryMetadataName = $"{CqrsRuntimeNamespace}.ICqrsHandlerRegistry";
private const string CqrsReflectionFallbackAttributeMetadataName =
$"{CqrsRuntimeNamespace}.CqrsReflectionFallbackAttribute";
private const string CqrsHandlerRegistryAttributeMetadataName = private const string CqrsHandlerRegistryAttributeMetadataName =
$"{CqrsRuntimeNamespace}.CqrsHandlerRegistryAttribute"; $"{CqrsRuntimeNamespace}.CqrsHandlerRegistryAttribute";
@ -28,8 +31,8 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
/// <inheritdoc /> /// <inheritdoc />
public void Initialize(IncrementalGeneratorInitializationContext context) public void Initialize(IncrementalGeneratorInitializationContext context)
{ {
var generationEnabled = context.CompilationProvider var generationEnvironment = context.CompilationProvider
.Select(static (compilation, _) => HasRequiredTypes(compilation)); .Select(static (compilation, _) => CreateGenerationEnvironment(compilation));
// Restrict semantic analysis to type declarations that can actually contribute implemented interfaces. // Restrict semantic analysis to type declarations that can actually contribute implemented interfaces.
var handlerCandidates = context.SyntaxProvider.CreateSyntaxProvider( var handlerCandidates = context.SyntaxProvider.CreateSyntaxProvider(
@ -39,19 +42,24 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
.Collect(); .Collect();
context.RegisterSourceOutput( context.RegisterSourceOutput(
generationEnabled.Combine(handlerCandidates), generationEnvironment.Combine(handlerCandidates),
static (productionContext, pair) => Execute(productionContext, pair.Left, pair.Right)); static (productionContext, pair) => Execute(productionContext, pair.Left, pair.Right));
} }
private static bool HasRequiredTypes(Compilation compilation) private static GenerationEnvironment CreateGenerationEnvironment(Compilation compilation)
{ {
return compilation.GetTypeByMetadataName(IRequestHandlerMetadataName) is not null && var generationEnabled = compilation.GetTypeByMetadataName(IRequestHandlerMetadataName) is not null &&
compilation.GetTypeByMetadataName(INotificationHandlerMetadataName) is not null && compilation.GetTypeByMetadataName(INotificationHandlerMetadataName) is not null &&
compilation.GetTypeByMetadataName(IStreamRequestHandlerMetadataName) is not null && compilation.GetTypeByMetadataName(IStreamRequestHandlerMetadataName) is not null &&
compilation.GetTypeByMetadataName(ICqrsHandlerRegistryMetadataName) is not null && compilation.GetTypeByMetadataName(ICqrsHandlerRegistryMetadataName) is not null &&
compilation.GetTypeByMetadataName(CqrsHandlerRegistryAttributeMetadataName) is not null && compilation.GetTypeByMetadataName(
compilation.GetTypeByMetadataName(ILoggerMetadataName) is not null && CqrsHandlerRegistryAttributeMetadataName) is not null &&
compilation.GetTypeByMetadataName(IServiceCollectionMetadataName) is not null; compilation.GetTypeByMetadataName(ILoggerMetadataName) is not null &&
compilation.GetTypeByMetadataName(IServiceCollectionMetadataName) is not null;
return new GenerationEnvironment(
generationEnabled,
compilation.GetTypeByMetadataName(CqrsReflectionFallbackAttributeMetadataName) is not null);
} }
private static bool IsHandlerCandidate(SyntaxNode node) private static bool IsHandlerCandidate(SyntaxNode node)
@ -108,21 +116,25 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
false); false);
} }
private static void Execute(SourceProductionContext context, bool generationEnabled, private static void Execute(SourceProductionContext context, GenerationEnvironment generationEnvironment,
ImmutableArray<HandlerCandidateAnalysis?> candidates) ImmutableArray<HandlerCandidateAnalysis?> candidates)
{ {
if (!generationEnabled) if (!generationEnvironment.GenerationEnabled)
return; return;
var registrations = CollectRegistrations(candidates, out var hasUnsupportedConcreteHandler); var registrations = CollectRegistrations(candidates, out var hasUnsupportedConcreteHandler);
// If the assembly contains handlers that generated code cannot legally reference if (registrations.Count == 0)
// (for example private nested handlers), keep the runtime on the reflection path
// so registration behavior remains complete instead of silently dropping handlers.
if (hasUnsupportedConcreteHandler || registrations.Count == 0)
return; return;
context.AddSource(HintName, GenerateSource(registrations)); // If the runtime contract does not yet expose the reflection fallback marker,
// keep the previous all-or-nothing behavior so unsupported handlers are not silently dropped.
if (hasUnsupportedConcreteHandler && !generationEnvironment.SupportsReflectionFallbackMarker)
return;
context.AddSource(
HintName,
GenerateSource(registrations, hasUnsupportedConcreteHandler));
} }
private static List<HandlerRegistrationSpec> CollectRegistrations( private static List<HandlerRegistrationSpec> CollectRegistrations(
@ -144,7 +156,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
if (candidate.Value.HasUnsupportedConcreteHandler) if (candidate.Value.HasUnsupportedConcreteHandler)
{ {
hasUnsupportedConcreteHandler = true; hasUnsupportedConcreteHandler = true;
return []; continue;
} }
uniqueCandidates[candidate.Value.ImplementationTypeDisplayName] = candidate.Value; uniqueCandidates[candidate.Value.ImplementationTypeDisplayName] = candidate.Value;
@ -270,7 +282,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return GetTypeSortKey(type).Replace("global::", string.Empty); return GetTypeSortKey(type).Replace("global::", string.Empty);
} }
private static string GenerateSource(IReadOnlyList<HandlerRegistrationSpec> registrations) private static string GenerateSource(
IReadOnlyList<HandlerRegistrationSpec> registrations,
bool emitReflectionFallbackAttribute)
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.AppendLine("// <auto-generated />"); builder.AppendLine("// <auto-generated />");
@ -283,6 +297,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
builder.Append('.'); builder.Append('.');
builder.Append(GeneratedTypeName); builder.Append(GeneratedTypeName);
builder.AppendLine("))]"); builder.AppendLine("))]");
if (emitReflectionFallbackAttribute)
{
builder.Append("[assembly: global::");
builder.Append(CqrsRuntimeNamespace);
builder.AppendLine(".CqrsReflectionFallbackAttribute()]");
}
builder.AppendLine(); builder.AppendLine();
builder.Append("namespace "); builder.Append("namespace ");
builder.Append(GeneratedNamespace); builder.Append(GeneratedNamespace);
@ -399,4 +420,8 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
} }
} }
} }
private readonly record struct GenerationEnvironment(
bool GenerationEnabled,
bool SupportsReflectionFallbackMarker);
} }