dotnet-cqrs/Svrnty.CQRS.Abstractions/Discovery/CommandMeta.cs

133 lines
5.1 KiB
C#

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Svrnty.CQRS.Abstractions.Attributes;
namespace Svrnty.CQRS.Abstractions.Discovery;
public sealed class CommandMeta : ICommandMeta
{
private readonly string _name;
private readonly string _lowerCamelCaseName;
public CommandMeta(Type commandType, Type serviceType, Type commandResultType)
{
CommandType = commandType;
ServiceType = serviceType;
CommandResultType = commandResultType;
// Cache reflection and computed values once in constructor
var nameAttribute = commandType.GetCustomAttribute<CommandNameAttribute>();
_name = nameAttribute?.Name ?? commandType.Name.Replace("Command", string.Empty);
_lowerCamelCaseName = ComputeLowerCamelCaseName(_name);
// Build compiled delegate for handler invocation
CompiledInvoker = BuildCompiledInvoker(serviceType, commandType, commandResultType);
}
public CommandMeta(Type commandType, Type serviceType)
{
CommandType = commandType;
ServiceType = serviceType;
// Cache reflection and computed values once in constructor
var nameAttribute = commandType.GetCustomAttribute<CommandNameAttribute>();
_name = nameAttribute?.Name ?? commandType.Name.Replace("Command", string.Empty);
_lowerCamelCaseName = ComputeLowerCamelCaseName(_name);
// Build compiled delegate for handler invocation
CompiledInvoker = BuildCompiledInvoker(serviceType, commandType, null);
}
private static string ComputeLowerCamelCaseName(string name)
{
if (string.IsNullOrEmpty(name))
return name;
var firstLetter = char.ToLowerInvariant(name[0]);
return $"{firstLetter}{name.Substring(1)}";
}
private static Func<object, object, CancellationToken, Task<object?>> BuildCompiledInvoker(
Type serviceType,
Type commandType,
Type? resultType)
{
// Parameters: (object handler, object command, CancellationToken ct)
var handlerParam = Expression.Parameter(typeof(object), "handler");
var commandParam = Expression.Parameter(typeof(object), "command");
var ctParam = Expression.Parameter(typeof(CancellationToken), "ct");
// Cast handler to actual handler type
var handlerCast = Expression.Convert(handlerParam, serviceType);
// Cast command to actual command type
var commandCast = Expression.Convert(commandParam, commandType);
// Get HandleAsync method with correct return type
var expectedReturnType = resultType == null ? typeof(Task) : typeof(Task<>).MakeGenericType(resultType);
var handleMethod = serviceType.GetMethod("HandleAsync",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { commandType, typeof(CancellationToken) },
null);
if (handleMethod == null || handleMethod.ReturnType != expectedReturnType)
throw new InvalidOperationException($"HandleAsync method with return type {expectedReturnType} not found on {serviceType}");
// Call handler.HandleAsync(command, ct)
var methodCall = Expression.Call(handlerCast, handleMethod, commandCast, ctParam);
Expression invokeCall;
if (resultType == null)
{
// Command without result: Task HandleAsync(...)
var asyncHelperMethod = typeof(CommandMeta).GetMethod(nameof(InvokeAndReturnNull), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!;
invokeCall = Expression.Call(asyncHelperMethod, methodCall);
}
else
{
// Command with result: Task<TResult> HandleAsync(...)
var asyncHelperMethod = typeof(CommandMeta).GetMethod(nameof(InvokeAndBox), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!
.MakeGenericMethod(resultType);
invokeCall = Expression.Call(asyncHelperMethod, methodCall);
}
// Compile to delegate
var lambda = Expression.Lambda<Func<object, object, CancellationToken, Task<object?>>>(
invokeCall,
handlerParam,
commandParam,
ctParam);
return lambda.Compile();
}
private static async Task<object?> InvokeAndReturnNull(Task task)
{
await task;
return null;
}
private static async Task<object?> InvokeAndBox<TResult>(Task<TResult> task)
{
var result = await task;
return result;
}
public string Name => _name;
public Type CommandType { get; }
public Type ServiceType { get; }
public Type CommandResultType { get; }
public string LowerCamelCaseName => _lowerCamelCaseName;
/// <summary>
/// Compiled delegate for invoking the handler without reflection.
/// Signature: (object handler, object command, CancellationToken ct) => Task<object?>
/// </summary>
public Func<object, object, CancellationToken, Task<object?>> CompiledInvoker { get; }
}