using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Tapeti.Config; using Tapeti.Helpers; namespace Tapeti.Default { /// /// /// Binding implementation for controller methods. Do not instantiate this class yourself, /// instead use the ITapetiConfigBuilder RegisterController / RegisterAllControllers extension /// methods. /// internal class ControllerMethodBinding : IControllerMethodBinding { /// /// Contains all the required information to bind a controller method to a queue. /// public struct BindingInfo { /// /// The controller type associated with this binding. /// public Type ControllerType; /// /// The method called when this binding is invoked. /// public MethodInfo Method; /// /// The queue this binding consumes. /// public QueueInfo QueueInfo; /// /// The message class handled by this binding's method. /// public Type MessageClass; /// /// Indicates whether this method accepts messages to the exchange by routing key, or direct-to-queue only. /// public BindingTargetMode BindingTargetMode; /// /// Indicates if the method or controller is marked with the Obsolete attribute, indicating it should /// only handle messages already in the queue and not bind to the routing key for new messages. /// public bool IsObsolete; /// /// Value factories for the method parameters. /// public IEnumerable ParameterFactories; /// /// The return value handler. /// public ResultHandler ResultHandler; /// /// Filter middleware as registered by the binding middleware. /// public IReadOnlyList FilterMiddleware; /// /// Message middleware as registered by the binding middleware. /// public IReadOnlyList MessageMiddleware; /// /// Cleanup middleware as registered by the binding middleware. /// public IReadOnlyList CleanupMiddleware; } private readonly IDependencyResolver dependencyResolver; private readonly BindingInfo bindingInfo; private readonly MessageHandlerFunc messageHandler; /// public string QueueName { get; private set; } /// public QueueType QueueType => bindingInfo.QueueInfo.QueueType; /// public Type Controller => bindingInfo.ControllerType; /// public MethodInfo Method => bindingInfo.Method; /// public ControllerMethodBinding(IDependencyResolver dependencyResolver, BindingInfo bindingInfo) { this.dependencyResolver = dependencyResolver; this.bindingInfo = bindingInfo; messageHandler = WrapMethod(bindingInfo.Method, bindingInfo.ParameterFactories, bindingInfo.ResultHandler); } /// public async Task Apply(IBindingTarget target) { if (!bindingInfo.IsObsolete) { switch (bindingInfo.BindingTargetMode) { case BindingTargetMode.Default: if (bindingInfo.QueueInfo.QueueType == QueueType.Dynamic) QueueName = await target.BindDynamic(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name); else { await target.BindDurable(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name); QueueName = bindingInfo.QueueInfo.Name; } break; case BindingTargetMode.Direct: if (bindingInfo.QueueInfo.QueueType == QueueType.Dynamic) QueueName = await target.BindDynamicDirect(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name); else { await target.BindDurableDirect(bindingInfo.QueueInfo.Name); QueueName = bindingInfo.QueueInfo.Name; } break; default: throw new ArgumentOutOfRangeException(nameof(bindingInfo.BindingTargetMode), bindingInfo.BindingTargetMode, "Invalid BindingTargetMode"); } } else if (bindingInfo.QueueInfo.QueueType == QueueType.Durable) { await target.BindDurableObsolete(bindingInfo.QueueInfo.Name); QueueName = bindingInfo.QueueInfo.Name; } } /// public bool Accept(Type messageClass) { return messageClass == bindingInfo.MessageClass; } /// public async Task Invoke(IMessageContext context) { var controller = dependencyResolver.Resolve(bindingInfo.ControllerType); using (var controllerContext = new ControllerMessageContext(context) { Controller = controller }) { if (!await FilterAllowed(controllerContext)) return; await MiddlewareHelper.GoAsync( bindingInfo.MessageMiddleware, async (handler, next) => await handler.Handle(controllerContext, next), async () => await messageHandler(controllerContext)); } } /// public async Task Cleanup(IMessageContext context, ConsumeResult consumeResult) { await MiddlewareHelper.GoAsync( bindingInfo.CleanupMiddleware, async (handler, next) => await handler.Cleanup(context, consumeResult, next), () => Task.CompletedTask); } private async Task FilterAllowed(IControllerMessageContext context) { var allowed = false; await MiddlewareHelper.GoAsync( bindingInfo.FilterMiddleware, async (handler, next) => await handler.Filter(context, next), () => { allowed = true; return Task.CompletedTask; }); return allowed; } private delegate Task MessageHandlerFunc(IControllerMessageContext context); private static MessageHandlerFunc WrapMethod(MethodInfo method, IEnumerable parameterFactories, ResultHandler resultHandler) { if (resultHandler != null) return WrapResultHandlerMethod(method, parameterFactories, resultHandler); if (method.ReturnType == typeof(void)) return WrapNullMethod(method, parameterFactories); if (method.ReturnType == typeof(Task)) return WrapTaskMethod(method, parameterFactories); if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) return WrapGenericTaskMethod(method, parameterFactories); return WrapObjectMethod(method, parameterFactories); } private static MessageHandlerFunc WrapResultHandlerMethod(MethodBase method, IEnumerable parameterFactories, ResultHandler resultHandler) { return context => { var result = method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray()); return resultHandler(context, result); }; } private static MessageHandlerFunc WrapNullMethod(MethodBase method, IEnumerable parameterFactories) { return context => { method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray()); return Task.CompletedTask; }; } private static MessageHandlerFunc WrapTaskMethod(MethodBase method, IEnumerable parameterFactories) { return context => (Task)method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray()); } private static MessageHandlerFunc WrapGenericTaskMethod(MethodBase method, IEnumerable parameterFactories) { return context => { return (Task)method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray()); }; } private static MessageHandlerFunc WrapObjectMethod(MethodBase method, IEnumerable parameterFactories) { return context => { return Task.FromResult(method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray())); }; } /// /// Contains information about the queue linked to the controller method. /// public class QueueInfo { /// /// The type of queue this binding consumes. /// public QueueType QueueType { get; set; } /// /// The name of the durable queue, or optional prefix of the dynamic queue. /// public string Name { get; set; } /// /// Determines if the QueueInfo properties contain a valid combination. /// public bool IsValid => QueueType == QueueType.Dynamic || !string.IsNullOrEmpty(Name); } } }