| | | 1 | | using System; |
| | | 2 | | using System.Collections.Concurrent; |
| | | 3 | | using System.Linq; |
| | | 4 | | using System.Threading; |
| | | 5 | | using System.Threading.Tasks; |
| | | 6 | | using Microsoft.Extensions.DependencyInjection; |
| | | 7 | | using NexMediator.Abstractions.Exceptions; |
| | | 8 | | using NexMediator.Abstractions.Interfaces; |
| | | 9 | | |
| | | 10 | | namespace NexMediator.Core.Internal; |
| | | 11 | | |
| | | 12 | | /// <summary> |
| | | 13 | | /// Builds and caches a pipeline invoker per request and response type. |
| | | 14 | | /// Supports strong typing of behaviors and correct ordering. |
| | | 15 | | /// </summary> |
| | | 16 | | internal static class NexRequestExecutor |
| | | 17 | | { |
| | 2 | 18 | | private static readonly ConcurrentDictionary< |
| | 2 | 19 | | (Type Request, Type Response), |
| | 2 | 20 | | Func<object, CancellationToken, Task<object>>> |
| | 2 | 21 | | _pipelineCache = new(); |
| | | 22 | | |
| | | 23 | | /// <summary> |
| | | 24 | | /// Dispatches a request through a cached pipeline and returns the typed response. |
| | | 25 | | /// </summary> |
| | | 26 | | /// <typeparam name="TResponse">The expected response type.</typeparam> |
| | | 27 | | /// <param name="request">The request to send.</param> |
| | | 28 | | /// <param name="provider">The root service provider.</param> |
| | | 29 | | /// <param name="cancellationToken">A token to cancel the operation.</param> |
| | | 30 | | /// <returns>The response from the handler.</returns> |
| | | 31 | | /// <exception cref="ArgumentNullException">Thrown if request is null.</exception> |
| | | 32 | | public static async Task<TResponse> Dispatch<TResponse>( |
| | | 33 | | INexRequest<TResponse> request, |
| | | 34 | | IServiceProvider provider, |
| | | 35 | | CancellationToken cancellationToken) |
| | 8 | 36 | | { |
| | 8 | 37 | | if (request is null) |
| | 0 | 38 | | throw new ArgumentNullException(nameof(request)); |
| | | 39 | | |
| | 8 | 40 | | var reqType = request.GetType(); |
| | 8 | 41 | | var resType = typeof(TResponse); |
| | 8 | 42 | | var key = (reqType, resType); |
| | | 43 | | |
| | 8 | 44 | | var invoker = _pipelineCache.GetOrAdd( |
| | 8 | 45 | | key, |
| | 12 | 46 | | _ => BuildFullPipelineInvoker(reqType, resType, provider)); |
| | | 47 | | |
| | 8 | 48 | | var result = await invoker(request!, cancellationToken) |
| | 8 | 49 | | .ConfigureAwait(false); |
| | 1 | 50 | | return (TResponse)result!; |
| | 1 | 51 | | } |
| | | 52 | | |
| | | 53 | | /// <summary> |
| | | 54 | | /// Builds a generic pipeline invoker by reflecting to a generic method. |
| | | 55 | | /// </summary> |
| | | 56 | | private static Func<object, CancellationToken, Task<object>> |
| | | 57 | | BuildFullPipelineInvoker( |
| | | 58 | | Type reqType, |
| | | 59 | | Type resType, |
| | | 60 | | IServiceProvider provider) |
| | 4 | 61 | | { |
| | 4 | 62 | | var method = typeof(NexRequestExecutor) |
| | 4 | 63 | | .GetMethod(nameof(BuildPipelineGeneric), System.Reflection.BindingFlags.NonPublic | System.Reflection.Bindin |
| | 4 | 64 | | var generic = method.MakeGenericMethod(reqType, resType); |
| | 4 | 65 | | return (Func<object, CancellationToken, Task<object>>) |
| | 4 | 66 | | generic.Invoke(null, new object[] { provider })!; |
| | 4 | 67 | | } |
| | | 68 | | |
| | | 69 | | /// <summary> |
| | | 70 | | /// Builds a strongly typed pipeline for TRequest and TResponse. |
| | | 71 | | /// </summary> |
| | | 72 | | private static Func<object, CancellationToken, Task<object>> |
| | | 73 | | BuildPipelineGeneric<TRequest, TResponse>(IServiceProvider provider) |
| | | 74 | | where TRequest : INexRequest<TResponse> |
| | 4 | 75 | | { |
| | 4 | 76 | | var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>(); |
| | | 77 | | |
| | 4 | 78 | | return async (object reqObj, CancellationToken ct) => |
| | 8 | 79 | | { |
| | 8 | 80 | | using var scope = scopeFactory.CreateScope(); |
| | 8 | 81 | | var sp = scope.ServiceProvider; |
| | 4 | 82 | | |
| | 4 | 83 | | // Resolve options and handler |
| | 8 | 84 | | var options = sp.GetRequiredService<NexMediatorOptions>(); |
| | 8 | 85 | | var handler = sp.GetRequiredService<INexRequestHandler<TRequest, TResponse>>(); |
| | 4 | 86 | | |
| | 4 | 87 | | // Base pipeline calls the handler |
| | 4 | 88 | | async Task<TResponse> HandlerPipe() => |
| | 1 | 89 | | await handler.Handle((TRequest)reqObj, ct) |
| | 1 | 90 | | .ConfigureAwait(false); |
| | 4 | 91 | | |
| | 7 | 92 | | RequestHandlerDelegate<TResponse> pipeline = HandlerPipe; |
| | 4 | 93 | | |
| | 4 | 94 | | // Resolve, order, and wrap behaviors |
| | 7 | 95 | | var behaviors = sp.GetServices<INexPipelineBehavior<TRequest, TResponse>>(); |
| | 7 | 96 | | var ordered = options.OrderBehaviors(behaviors.Cast<object>()); |
| | 4 | 97 | | |
| | 33 | 98 | | foreach (var behaviorObj in ordered.Reverse()) |
| | 6 | 99 | | { |
| | 6 | 100 | | var behavior = (INexPipelineBehavior<TRequest, TResponse>)behaviorObj; |
| | 6 | 101 | | var next = pipeline; |
| | 4 | 102 | | |
| | 4 | 103 | | async Task<TResponse> BehaviorPipe() => |
| | 6 | 104 | | await behavior.Handle((TRequest)reqObj, next, ct) |
| | 6 | 105 | | .ConfigureAwait(false); |
| | 4 | 106 | | |
| | 6 | 107 | | pipeline = BehaviorPipe; |
| | 6 | 108 | | } |
| | 4 | 109 | | |
| | 4 | 110 | | // Execute and return |
| | 7 | 111 | | var response = await pipeline().ConfigureAwait(false); |
| | 1 | 112 | | if (response is null) |
| | 0 | 113 | | throw new NexMediatorException("Handler returned null response."); |
| | 4 | 114 | | |
| | 1 | 115 | | return (object)response!; |
| | 5 | 116 | | }; |
| | 4 | 117 | | } |
| | | 118 | | } |