133 lines
5.1 KiB
C#
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; }
|
|
}
|
|
|