2020-06-11 14:36:55 +00:00
using System ;
2019-08-14 18:48:40 +00:00
using System.Collections.Generic ;
using System.Linq ;
2019-08-13 18:30:04 +00:00
using System.Reflection ;
using System.Threading.Tasks ;
using Tapeti.Config ;
2019-08-14 18:48:40 +00:00
using Tapeti.Helpers ;
2019-08-13 18:30:04 +00:00
namespace Tapeti.Default
{
/// <inheritdoc />
/// <summary>
/// Binding implementation for controller methods. Do not instantiate this class yourself,
/// instead use the ITapetiConfigBuilder RegisterController / RegisterAllControllers extension
/// methods.
/// </summary>
2019-08-15 10:10:29 +00:00
internal class ControllerMethodBinding : IControllerMethodBinding
2019-08-13 18:30:04 +00:00
{
2019-08-14 18:48:40 +00:00
/// <summary>
/// Contains all the required information to bind a controller method to a queue.
/// </summary>
public struct BindingInfo
{
/// <summary>
/// The controller type associated with this binding.
/// </summary>
public Type ControllerType ;
/// <summary>
/// The method called when this binding is invoked.
/// </summary>
public MethodInfo Method ;
/// <summary>
/// The queue this binding consumes.
/// </summary>
public QueueInfo QueueInfo ;
/// <summary>
/// The message class handled by this binding's method.
/// </summary>
public Type MessageClass ;
/// <summary>
/// Indicates whether this method accepts messages to the exchange by routing key, or direct-to-queue only.
/// </summary>
public BindingTargetMode BindingTargetMode ;
2019-08-20 09:47:53 +00:00
/// <summary>
/// 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.
/// </summary>
public bool IsObsolete ;
2019-08-14 18:48:40 +00:00
/// <summary>
/// Value factories for the method parameters.
/// </summary>
public IEnumerable < ValueFactory > ParameterFactories ;
/// <summary>
/// The return value handler.
/// </summary>
public ResultHandler ResultHandler ;
/// <summary>
/// Filter middleware as registered by the binding middleware.
/// </summary>
public IReadOnlyList < IControllerFilterMiddleware > FilterMiddleware ;
/// <summary>
/// Message middleware as registered by the binding middleware.
/// </summary>
public IReadOnlyList < IControllerMessageMiddleware > MessageMiddleware ;
/// <summary>
/// Cleanup middleware as registered by the binding middleware.
/// </summary>
public IReadOnlyList < IControllerCleanupMiddleware > CleanupMiddleware ;
}
private readonly IDependencyResolver dependencyResolver ;
private readonly BindingInfo bindingInfo ;
private readonly MessageHandlerFunc messageHandler ;
2019-08-13 18:30:04 +00:00
/// <inheritdoc />
public string QueueName { get ; private set ; }
2019-08-19 07:33:07 +00:00
/// <inheritdoc />
public QueueType QueueType = > bindingInfo . QueueInfo . QueueType ;
2019-08-15 10:10:29 +00:00
/// <inheritdoc />
public Type Controller = > bindingInfo . ControllerType ;
/// <inheritdoc />
public MethodInfo Method = > bindingInfo . Method ;
2019-08-13 18:30:04 +00:00
2019-08-14 18:48:40 +00:00
public ControllerMethodBinding ( IDependencyResolver dependencyResolver , BindingInfo bindingInfo )
2019-08-13 18:30:04 +00:00
{
2019-08-14 18:48:40 +00:00
this . dependencyResolver = dependencyResolver ;
this . bindingInfo = bindingInfo ;
messageHandler = WrapMethod ( bindingInfo . Method , bindingInfo . ParameterFactories , bindingInfo . ResultHandler ) ;
2019-08-13 18:30:04 +00:00
}
/// <inheritdoc />
2022-02-09 11:42:05 +00:00
public async ValueTask Apply ( IBindingTarget target )
2019-08-13 18:30:04 +00:00
{
2019-08-20 09:47:53 +00:00
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 )
2019-08-14 18:48:40 +00:00
{
2019-08-20 09:47:53 +00:00
await target . BindDurableObsolete ( bindingInfo . QueueInfo . Name ) ;
QueueName = bindingInfo . QueueInfo . Name ;
2019-08-14 18:48:40 +00:00
}
2019-08-13 18:30:04 +00:00
}
/// <inheritdoc />
public bool Accept ( Type messageClass )
{
2019-08-14 18:48:40 +00:00
return messageClass = = bindingInfo . MessageClass ;
2019-08-13 18:30:04 +00:00
}
2019-08-14 18:48:40 +00:00
2019-08-13 18:30:04 +00:00
/// <inheritdoc />
2022-02-09 11:42:05 +00:00
public async ValueTask Invoke ( IMessageContext context )
2019-08-13 18:30:04 +00:00
{
2022-02-09 10:26:56 +00:00
var controller = Method . IsStatic ? null : dependencyResolver . Resolve ( bindingInfo . ControllerType ) ;
2021-09-02 14:16:11 +00:00
context . Store ( new ControllerMessageContextPayload ( controller , context . Binding as IControllerMethodBinding ) ) ;
2021-07-18 11:27:10 +00:00
2021-09-02 14:16:11 +00:00
if ( ! await FilterAllowed ( context ) )
2021-07-18 11:27:10 +00:00
return ;
2019-08-14 18:48:40 +00:00
2021-07-18 11:27:10 +00:00
await MiddlewareHelper . GoAsync (
bindingInfo . MessageMiddleware ,
2021-09-02 14:16:11 +00:00
async ( handler , next ) = > await handler . Handle ( context , next ) ,
async ( ) = > await messageHandler ( context ) ) ;
2019-08-14 18:48:40 +00:00
}
/// <inheritdoc />
2022-02-09 11:42:05 +00:00
public async ValueTask Cleanup ( IMessageContext context , ConsumeResult consumeResult )
2019-08-14 18:48:40 +00:00
{
2021-07-18 11:27:10 +00:00
await MiddlewareHelper . GoAsync (
bindingInfo . CleanupMiddleware ,
2021-09-02 14:16:11 +00:00
async ( handler , next ) = > await handler . Cleanup ( context , consumeResult , next ) ,
2022-02-09 10:26:56 +00:00
( ) = > default ) ;
2019-08-14 18:48:40 +00:00
}
2021-09-02 14:16:11 +00:00
private async Task < bool > FilterAllowed ( IMessageContext context )
2019-08-14 18:48:40 +00:00
{
var allowed = false ;
await MiddlewareHelper . GoAsync (
bindingInfo . FilterMiddleware ,
async ( handler , next ) = > await handler . Filter ( context , next ) ,
( ) = >
{
allowed = true ;
2022-02-09 10:26:56 +00:00
return default ;
2019-08-14 18:48:40 +00:00
} ) ;
return allowed ;
}
2022-02-09 10:26:56 +00:00
private delegate ValueTask MessageHandlerFunc ( IMessageContext context ) ;
2019-08-14 18:48:40 +00:00
2021-07-18 11:49:01 +00:00
private MessageHandlerFunc WrapMethod ( MethodInfo method , IEnumerable < ValueFactory > parameterFactories , ResultHandler resultHandler )
2019-08-14 18:48:40 +00:00
{
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 ) ;
2022-02-09 10:26:56 +00:00
if ( method . ReturnType = = typeof ( ValueTask ) )
return WrapValueTaskMethod ( method , parameterFactories ) ;
2019-08-14 18:48:40 +00:00
2022-02-09 10:26:56 +00:00
// Breaking change in Tapeti 2.9: PublishResultBinding or other middleware should have taken care of the return value. If not, don't silently discard it.
throw new ArgumentException ( $"Method {method.Name} on controller {method.DeclaringType?.FullName} returns type {method.ReturnType.FullName}, which can not be handled by Tapeti or any registered middleware" ) ;
2019-08-14 18:48:40 +00:00
}
2021-07-18 11:49:01 +00:00
private MessageHandlerFunc WrapResultHandlerMethod ( MethodBase method , IEnumerable < ValueFactory > parameterFactories , ResultHandler resultHandler )
2019-08-14 18:48:40 +00:00
{
return context = >
{
2021-09-02 14:16:11 +00:00
var controllerPayload = context . Get < ControllerMessageContextPayload > ( ) ;
2021-07-18 11:49:01 +00:00
try
{
2021-09-02 14:16:11 +00:00
var result = method . Invoke ( controllerPayload . Controller , parameterFactories . Select ( p = > p ( context ) ) . ToArray ( ) ) ;
2021-07-18 11:49:01 +00:00
return resultHandler ( context , result ) ;
}
catch ( Exception e )
{
AddExceptionData ( e ) ;
throw ;
}
2019-08-14 18:48:40 +00:00
} ;
2019-08-13 18:30:04 +00:00
}
2021-07-18 11:49:01 +00:00
private MessageHandlerFunc WrapNullMethod ( MethodBase method , IEnumerable < ValueFactory > parameterFactories )
2019-08-14 18:48:40 +00:00
{
return context = >
{
2021-09-02 14:16:11 +00:00
var controllerPayload = context . Get < ControllerMessageContextPayload > ( ) ;
2021-07-18 11:49:01 +00:00
try
{
2021-09-02 14:16:11 +00:00
method . Invoke ( controllerPayload . Controller , parameterFactories . Select ( p = > p ( context ) ) . ToArray ( ) ) ;
2022-02-09 10:26:56 +00:00
return default ;
2021-07-18 11:49:01 +00:00
}
catch ( Exception e )
{
AddExceptionData ( e ) ;
throw ;
}
2019-08-14 18:48:40 +00:00
} ;
}
2021-07-18 11:49:01 +00:00
private MessageHandlerFunc WrapTaskMethod ( MethodBase method , IEnumerable < ValueFactory > parameterFactories )
2019-08-14 18:48:40 +00:00
{
2021-07-18 11:49:01 +00:00
return context = >
{
2021-09-02 14:16:11 +00:00
var controllerPayload = context . Get < ControllerMessageContextPayload > ( ) ;
2021-07-18 11:49:01 +00:00
try
{
2022-02-09 10:26:56 +00:00
return new ValueTask ( ( Task ) method . Invoke ( controllerPayload . Controller , parameterFactories . Select ( p = > p ( context ) ) . ToArray ( ) ) ) ;
2021-07-18 11:49:01 +00:00
}
catch ( Exception e )
{
AddExceptionData ( e ) ;
throw ;
}
} ;
2019-08-14 18:48:40 +00:00
}
2022-02-09 10:26:56 +00:00
private MessageHandlerFunc WrapValueTaskMethod ( MethodBase method , IEnumerable < ValueFactory > parameterFactories )
2019-08-14 18:48:40 +00:00
{
return context = >
{
2021-09-02 14:16:11 +00:00
var controllerPayload = context . Get < ControllerMessageContextPayload > ( ) ;
2021-07-18 11:49:01 +00:00
try
{
2022-02-09 10:26:56 +00:00
return ( ValueTask ) method . Invoke ( controllerPayload . Controller , parameterFactories . Select ( p = > p ( context ) ) . ToArray ( ) ) ;
2021-07-18 11:49:01 +00:00
}
catch ( Exception e )
{
AddExceptionData ( e ) ;
throw ;
}
2019-08-14 18:48:40 +00:00
} ;
}
2021-07-18 11:49:01 +00:00
private void AddExceptionData ( Exception exception )
{
exception . Data [ "Tapeti.Controller.Name" ] = bindingInfo . ControllerType ? . FullName ;
exception . Data [ "Tapeti.Controller.Method" ] = bindingInfo . Method ? . Name ;
}
2019-08-13 18:30:04 +00:00
/// <summary>
2019-08-14 18:48:40 +00:00
/// Contains information about the queue linked to the controller method.
2019-08-13 18:30:04 +00:00
/// </summary>
public class QueueInfo
{
/// <summary>
2019-08-19 07:33:07 +00:00
/// The type of queue this binding consumes.
2019-08-13 18:30:04 +00:00
/// </summary>
2019-08-19 07:33:07 +00:00
public QueueType QueueType { get ; set ; }
2019-08-13 18:30:04 +00:00
/// <summary>
/// The name of the durable queue, or optional prefix of the dynamic queue.
/// </summary>
public string Name { get ; set ; }
/// <summary>
/// Determines if the QueueInfo properties contain a valid combination.
/// </summary>
2019-08-19 07:33:07 +00:00
public bool IsValid = > QueueType = = QueueType . Dynamic | | ! string . IsNullOrEmpty ( Name ) ;
2019-08-13 18:30:04 +00:00
}
}
}