| | | 1 | | using Dom.Mediator.Abstractions; |
| | | 2 | | using Dom.Mediator.Models; |
| | | 3 | | using Microsoft.Extensions.DependencyInjection; |
| | | 4 | | using System.Data; |
| | | 5 | | using System.Reflection; |
| | | 6 | | |
| | | 7 | | namespace Dom.Mediator.Implementation; |
| | | 8 | | |
| | | 9 | | public class Mediator : IMediator |
| | | 10 | | { |
| | | 11 | | private const int BEHAVIOUR_COMMAND_ARGS = 1; |
| | | 12 | | private const int BEHAVIOUR_QUERY_ARGS = 2; |
| | | 13 | | |
| | 12 | 14 | | private readonly Dictionary<Type, Type> _queryHandler = new(); |
| | 12 | 15 | | private readonly Dictionary<Type, Type> _commandHandler = new(); |
| | | 16 | | private readonly IServiceProvider _serviceProvider; |
| | | 17 | | |
| | 12 | 18 | | private readonly List<Type> _behaviours = new(); |
| | | 19 | | |
| | 12 | 20 | | public Mediator(IServiceProvider serviceProvider) |
| | | 21 | | { |
| | 12 | 22 | | _serviceProvider = serviceProvider; |
| | 12 | 23 | | } |
| | | 24 | | |
| | | 25 | | #region INIT |
| | | 26 | | public void RegisterHandlers(params Assembly[] assemblies) |
| | | 27 | | { |
| | 24 | 28 | | var types = assemblies.SelectMany(a => a.GetTypes()) |
| | 240 | 29 | | .Where(t => !t.IsAbstract && !t.IsInterface) |
| | 12 | 30 | | .ToList(); |
| | | 31 | | |
| | 504 | 32 | | foreach (var type in types) |
| | | 33 | | { |
| | 936 | 34 | | foreach (var iface in type.GetInterfaces()) |
| | | 35 | | { |
| | 228 | 36 | | if (!iface.IsGenericType) continue; |
| | | 37 | | |
| | 120 | 38 | | var def = iface.GetGenericTypeDefinition(); |
| | | 39 | | |
| | 120 | 40 | | if (def == typeof(IQueryHandler<,>)) |
| | | 41 | | { |
| | 12 | 42 | | var requestType = iface.GetGenericArguments()[0]; |
| | | 43 | | |
| | 12 | 44 | | if (requestType.IsNotPublic) |
| | 0 | 45 | | throw new MediatorException($"The type '{type}' which defines the 'Query' must be declared as pu |
| | | 46 | | |
| | 12 | 47 | | if (type.IsNotPublic) |
| | 0 | 48 | | throw new MediatorException($"The type '{type}' which implements a 'Query Handler' must be decla |
| | | 49 | | |
| | 12 | 50 | | _queryHandler[requestType] = type; |
| | | 51 | | } |
| | 108 | 52 | | else if ( |
| | 108 | 53 | | def == typeof(ICommandHandler<>) || |
| | 108 | 54 | | def == typeof(ICommandHandler<,>)) |
| | | 55 | | { |
| | 24 | 56 | | var commandType = iface.GetGenericArguments()[0]; |
| | | 57 | | |
| | 24 | 58 | | if (commandType.IsNotPublic) |
| | 0 | 59 | | throw new MediatorException($"The type '{commandType}' which defines the 'Command' must be decla |
| | | 60 | | |
| | 24 | 61 | | if (type.IsNotPublic) |
| | 0 | 62 | | throw new MediatorException($"The type '{type}' which implements a 'Command Handler' must be dec |
| | | 63 | | |
| | 24 | 64 | | _commandHandler[commandType] = type; |
| | | 65 | | } |
| | | 66 | | } |
| | | 67 | | } |
| | 12 | 68 | | } |
| | | 69 | | |
| | | 70 | | public void AddBehaviour(Type behaviourType) |
| | | 71 | | { |
| | 0 | 72 | | if (!behaviourType.IsGenericType) |
| | 0 | 73 | | throw new ArgumentException("Behaviour type must be generic", nameof(behaviourType)); |
| | | 74 | | |
| | 0 | 75 | | var genericTypeDefinition = behaviourType.GetGenericTypeDefinition(); |
| | 0 | 76 | | _behaviours.Add(genericTypeDefinition); |
| | 0 | 77 | | } |
| | | 78 | | |
| | | 79 | | #endregion |
| | | 80 | | |
| | | 81 | | #region MEDIATOR METHODS |
| | | 82 | | public async Task<Result<TResponse>> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToke |
| | | 83 | | { |
| | 8 | 84 | | var handlerRes = GetHandlerType(request); |
| | 6 | 85 | | var requestType = handlerRes.Item1; |
| | | 86 | | |
| | 12 | 87 | | RequestHandlerDelegate<TResponse> next = () => handlerRes.Item2.Handle((dynamic)request, cancellationToken); |
| | | 88 | | |
| | | 89 | | // Apply behaviors in reverse order (so the first registered behavior is the outermost) |
| | 12 | 90 | | foreach (var behaviourType in _behaviours.AsEnumerable().Reverse()) |
| | | 91 | | { |
| | | 92 | | // Check if this behavior has the right arity (2 type parameters) |
| | 0 | 93 | | if (behaviourType.GetGenericArguments().Length == BEHAVIOUR_QUERY_ARGS) |
| | | 94 | | { |
| | 0 | 95 | | var concreteBehaviourType = behaviourType.MakeGenericType(requestType, typeof(TResponse)); |
| | | 96 | | |
| | 0 | 97 | | object? behaviour = ActivatorUtilities.CreateInstance(_serviceProvider, concreteBehaviourType); |
| | | 98 | | |
| | 0 | 99 | | var currentNext = next; |
| | 0 | 100 | | next = () => ((dynamic)behaviour).Handle((dynamic)request, cancellationToken, currentNext); |
| | | 101 | | } |
| | | 102 | | // Skip behaviors with different arity |
| | | 103 | | } |
| | | 104 | | |
| | 6 | 105 | | return await next(); |
| | 6 | 106 | | } |
| | | 107 | | |
| | | 108 | | /// <summary> |
| | | 109 | | /// Sends a command that doesn't return a value |
| | | 110 | | /// </summary> |
| | | 111 | | public async Task<Result> Send(ICommand command, CancellationToken cancellationToken = default) |
| | | 112 | | { |
| | 4 | 113 | | var handlerRes = GetHandlerType(command); |
| | 4 | 114 | | var commandType = handlerRes.Item1; |
| | | 115 | | |
| | 8 | 116 | | CommandHandlerDelegate next = () => handlerRes.Item2.Handle((dynamic)command, cancellationToken); |
| | | 117 | | |
| | | 118 | | // Apply behaviors in reverse order (so the first registered behavior is the outermost) |
| | 8 | 119 | | foreach (var behaviourType in _behaviours.AsEnumerable().Reverse()) |
| | | 120 | | { |
| | | 121 | | // Check if this behavior has the right arity (1 type parameter) |
| | 0 | 122 | | if (behaviourType.GetGenericArguments().Length == BEHAVIOUR_COMMAND_ARGS) |
| | | 123 | | { |
| | 0 | 124 | | var concreteBehaviourType = behaviourType.MakeGenericType(commandType); |
| | | 125 | | |
| | 0 | 126 | | object? behaviour = ActivatorUtilities.CreateInstance(_serviceProvider, concreteBehaviourType); |
| | | 127 | | |
| | 0 | 128 | | var currentNext = next; |
| | 0 | 129 | | next = () => ((dynamic)behaviour).Handle((dynamic)command, cancellationToken, currentNext); |
| | | 130 | | } |
| | | 131 | | // Skip behaviors with different arity |
| | | 132 | | } |
| | | 133 | | |
| | 4 | 134 | | return await next(); |
| | 4 | 135 | | } |
| | | 136 | | #endregion |
| | | 137 | | |
| | | 138 | | #region PRIVATE |
| | | 139 | | |
| | | 140 | | private dynamic GetHandler(Type requestType) |
| | | 141 | | { |
| | 12 | 142 | | if (_queryHandler.TryGetValue(requestType, out var handlerType)) |
| | | 143 | | { |
| | 2 | 144 | | return ActivatorUtilities.CreateInstance(_serviceProvider, handlerType); |
| | | 145 | | } |
| | 10 | 146 | | else if (_commandHandler.TryGetValue(requestType, out var commandHandlerType)) |
| | | 147 | | { |
| | 8 | 148 | | return ActivatorUtilities.CreateInstance(_serviceProvider, commandHandlerType); |
| | | 149 | | } |
| | | 150 | | |
| | 2 | 151 | | throw new MediatorException($"Handler not registered for type: {requestType.Name}"); |
| | | 152 | | } |
| | | 153 | | |
| | | 154 | | private (Type, dynamic) GetHandlerType(object request) |
| | | 155 | | { |
| | 12 | 156 | | var requestType = request.GetType(); |
| | 12 | 157 | | dynamic handler = GetHandler(requestType); |
| | | 158 | | |
| | 10 | 159 | | if (handler is null) |
| | 0 | 160 | | throw new MediatorException($"Handler not registered for type: {requestType.Name}"); |
| | | 161 | | |
| | 10 | 162 | | return (requestType, handler); |
| | | 163 | | } |
| | | 164 | | |
| | | 165 | | |
| | | 166 | | #endregion |
| | | 167 | | } |