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)
{
using (var controllerContext = new ControllerMessageContext(context)
{
Controller = null
})
{
await MiddlewareHelper.GoAsync(
bindingInfo.CleanupMiddleware,
async (handler, next) => await handler.Cleanup(controllerContext, 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