Implemented #31: Include message details in exception logging (optionally)
Refactored IControllerMessageContext into context payloads to get access to it in the exception handler
This commit is contained in:
parent
5a90c1e0a5
commit
be576a2409
@ -1,20 +0,0 @@
|
|||||||
namespace Tapeti.Flow
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Key names as used in the message context store. For internal use.
|
|
||||||
/// </summary>
|
|
||||||
public static class ContextItems
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Key given to the FlowContext object as stored in the message context.
|
|
||||||
/// </summary>
|
|
||||||
public const string FlowContext = "Tapeti.Flow.FlowContext";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates if the current message handler is the last one to be called before a
|
|
||||||
/// parallel flow is done and the convergeMethod will be called.
|
|
||||||
/// Temporarily disables storing the flow state.
|
|
||||||
/// </summary>
|
|
||||||
public const string FlowIsConverging = "Tapeti.Flow.IsConverging";
|
|
||||||
}
|
|
||||||
}
|
|
@ -74,16 +74,16 @@ namespace Tapeti.Flow.Default
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Task HandleYieldPoint(IControllerMessageContext context, IYieldPoint yieldPoint)
|
private static Task HandleYieldPoint(IMessageContext context, IYieldPoint yieldPoint)
|
||||||
{
|
{
|
||||||
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
||||||
return flowHandler.Execute(new FlowHandlerContext(context), yieldPoint);
|
return flowHandler.Execute(new FlowHandlerContext(context), yieldPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Task HandleParallelResponse(IControllerMessageContext context)
|
private static Task HandleParallelResponse(IMessageContext context)
|
||||||
{
|
{
|
||||||
if (context.Get<object>(ContextItems.FlowIsConverging, out _))
|
if (context.TryGet<FlowMessageContextPayload>(out var flowPayload) && flowPayload.FlowIsConverging)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
||||||
|
@ -12,24 +12,31 @@ namespace Tapeti.Flow.Default
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class FlowContinuationMiddleware : IControllerFilterMiddleware, IControllerMessageMiddleware, IControllerCleanupMiddleware
|
internal class FlowContinuationMiddleware : IControllerFilterMiddleware, IControllerMessageMiddleware, IControllerCleanupMiddleware
|
||||||
{
|
{
|
||||||
public async Task Filter(IControllerMessageContext context, Func<Task> next)
|
public async Task Filter(IMessageContext context, Func<Task> next)
|
||||||
{
|
{
|
||||||
|
if (!context.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||||
|
return;
|
||||||
|
|
||||||
var flowContext = await EnrichWithFlowContext(context);
|
var flowContext = await EnrichWithFlowContext(context);
|
||||||
if (flowContext?.ContinuationMetadata == null)
|
if (flowContext?.ContinuationMetadata == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (flowContext.ContinuationMetadata.MethodName != MethodSerializer.Serialize(context.Binding.Method))
|
if (flowContext.ContinuationMetadata.MethodName != MethodSerializer.Serialize(controllerPayload.Binding.Method))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task Handle(IControllerMessageContext context, Func<Task> next)
|
public async Task Handle(IMessageContext context, Func<Task> next)
|
||||||
{
|
{
|
||||||
if (context.Get(ContextItems.FlowContext, out FlowContext flowContext))
|
if (!context.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (context.TryGet<FlowMessageContextPayload>(out var flowPayload))
|
||||||
{
|
{
|
||||||
Newtonsoft.Json.JsonConvert.PopulateObject(flowContext.FlowState.Data, context.Controller);
|
var flowContext = flowPayload.FlowContext;
|
||||||
|
Newtonsoft.Json.JsonConvert.PopulateObject(flowContext.FlowState.Data, controllerPayload.Controller);
|
||||||
|
|
||||||
// Remove Continuation now because the IYieldPoint result handler will store the new state
|
// Remove Continuation now because the IYieldPoint result handler will store the new state
|
||||||
flowContext.FlowState.Continuations.Remove(flowContext.ContinuationID);
|
flowContext.FlowState.Continuations.Remove(flowContext.ContinuationID);
|
||||||
@ -38,12 +45,12 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
if (converge)
|
if (converge)
|
||||||
// Indicate to the FlowBindingMiddleware that the state must not to be stored
|
// Indicate to the FlowBindingMiddleware that the state must not to be stored
|
||||||
context.Store(ContextItems.FlowIsConverging, null);
|
flowPayload.FlowIsConverging = true;
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
|
|
||||||
if (converge)
|
if (converge)
|
||||||
await CallConvergeMethod(context,
|
await CallConvergeMethod(context, controllerPayload,
|
||||||
flowContext.ContinuationMetadata.ConvergeMethodName,
|
flowContext.ContinuationMetadata.ConvergeMethodName,
|
||||||
flowContext.ContinuationMetadata.ConvergeMethodSync);
|
flowContext.ContinuationMetadata.ConvergeMethodSync);
|
||||||
}
|
}
|
||||||
@ -52,14 +59,19 @@ namespace Tapeti.Flow.Default
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task Cleanup(IControllerMessageContext context, ConsumeResult consumeResult, Func<Task> next)
|
public async Task Cleanup(IMessageContext context, ConsumeResult consumeResult, Func<Task> next)
|
||||||
{
|
{
|
||||||
await next();
|
await next();
|
||||||
|
|
||||||
if (!context.Get(ContextItems.FlowContext, out FlowContext flowContext))
|
if (!context.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (flowContext.ContinuationMetadata.MethodName != MethodSerializer.Serialize(context.Binding.Method))
|
if (!context.TryGet<FlowMessageContextPayload>(out var flowPayload))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var flowContext = flowPayload.FlowContext;
|
||||||
|
|
||||||
|
if (flowContext.ContinuationMetadata.MethodName != MethodSerializer.Serialize(controllerPayload.Binding.Method))
|
||||||
// Do not call when the controller method was filtered, if the same message has two methods
|
// Do not call when the controller method was filtered, if the same message has two methods
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -76,10 +88,10 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static async Task<FlowContext> EnrichWithFlowContext(IControllerMessageContext context)
|
private static async Task<FlowContext> EnrichWithFlowContext(IMessageContext context)
|
||||||
{
|
{
|
||||||
if (context.Get(ContextItems.FlowContext, out FlowContext flowContext))
|
if (context.TryGet<FlowMessageContextPayload>(out var flowPayload))
|
||||||
return flowContext;
|
return flowPayload.FlowContext;
|
||||||
|
|
||||||
|
|
||||||
if (context.Properties.CorrelationId == null)
|
if (context.Properties.CorrelationId == null)
|
||||||
@ -100,7 +112,7 @@ namespace Tapeti.Flow.Default
|
|||||||
if (flowState == null)
|
if (flowState == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
flowContext = new FlowContext
|
var flowContext = new FlowContext
|
||||||
{
|
{
|
||||||
HandlerContext = new FlowHandlerContext(context),
|
HandlerContext = new FlowHandlerContext(context),
|
||||||
|
|
||||||
@ -112,26 +124,28 @@ namespace Tapeti.Flow.Default
|
|||||||
};
|
};
|
||||||
|
|
||||||
// IDisposable items in the IMessageContext are automatically disposed
|
// IDisposable items in the IMessageContext are automatically disposed
|
||||||
context.Store(ContextItems.FlowContext, flowContext);
|
context.Store(new FlowMessageContextPayload(flowContext));
|
||||||
return flowContext;
|
return flowContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static async Task CallConvergeMethod(IControllerMessageContext context, string methodName, bool sync)
|
private static async Task CallConvergeMethod(IMessageContext context, ControllerMessageContextPayload controllerPayload, string methodName, bool sync)
|
||||||
{
|
{
|
||||||
IYieldPoint yieldPoint;
|
IYieldPoint yieldPoint;
|
||||||
|
|
||||||
var method = context.Controller.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
|
|
||||||
|
var method = controllerPayload.Controller.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
if (method == null)
|
if (method == null)
|
||||||
throw new ArgumentException($"Unknown converge method in controller {context.Controller.GetType().Name}: {methodName}");
|
throw new ArgumentException($"Unknown converge method in controller {controllerPayload.Controller.GetType().Name}: {methodName}");
|
||||||
|
|
||||||
if (sync)
|
if (sync)
|
||||||
yieldPoint = (IYieldPoint)method.Invoke(context.Controller, new object[] {});
|
yieldPoint = (IYieldPoint)method.Invoke(controllerPayload.Controller, new object[] {});
|
||||||
else
|
else
|
||||||
yieldPoint = await (Task<IYieldPoint>)method.Invoke(context.Controller, new object[] { });
|
yieldPoint = await (Task<IYieldPoint>)method.Invoke(controllerPayload.Controller, new object[] { });
|
||||||
|
|
||||||
if (yieldPoint == null)
|
if (yieldPoint == null)
|
||||||
throw new YieldPointException($"Yield point is required in controller {context.Controller.GetType().Name} for converge method {methodName}");
|
throw new YieldPointException($"Yield point is required in controller {controllerPayload.Controller.GetType().Name} for converge method {methodName}");
|
||||||
|
|
||||||
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
||||||
await flowHandler.Execute(new FlowHandlerContext(context), yieldPoint);
|
await flowHandler.Execute(new FlowHandlerContext(context), yieldPoint);
|
||||||
|
@ -18,15 +18,18 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FlowHandlerContext(IControllerMessageContext source)
|
public FlowHandlerContext(IMessageContext source)
|
||||||
{
|
{
|
||||||
if (source == null)
|
if (source == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!source.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||||
|
return;
|
||||||
|
|
||||||
Config = source.Config;
|
Config = source.Config;
|
||||||
Controller = source.Controller;
|
Controller = controllerPayload.Controller;
|
||||||
Method = source.Binding.Method;
|
Method = controllerPayload.Binding.Method;
|
||||||
ControllerMessageContext = source;
|
MessageContext = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -45,6 +48,6 @@ namespace Tapeti.Flow.Default
|
|||||||
public MethodInfo Method { get; set; }
|
public MethodInfo Method { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IControllerMessageContext ControllerMessageContext { get; set; }
|
public IMessageContext MessageContext { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,16 +162,16 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
private static ReplyMetadata GetReply(IFlowHandlerContext context)
|
private static ReplyMetadata GetReply(IFlowHandlerContext context)
|
||||||
{
|
{
|
||||||
var requestAttribute = context.ControllerMessageContext?.Message?.GetType().GetCustomAttribute<RequestAttribute>();
|
var requestAttribute = context.MessageContext?.Message?.GetType().GetCustomAttribute<RequestAttribute>();
|
||||||
if (requestAttribute?.Response == null)
|
if (requestAttribute?.Response == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new ReplyMetadata
|
return new ReplyMetadata
|
||||||
{
|
{
|
||||||
CorrelationId = context.ControllerMessageContext.Properties.CorrelationId,
|
CorrelationId = context.MessageContext.Properties.CorrelationId,
|
||||||
ReplyTo = context.ControllerMessageContext.Properties.ReplyTo,
|
ReplyTo = context.MessageContext.Properties.ReplyTo,
|
||||||
ResponseTypeName = requestAttribute.Response.FullName,
|
ResponseTypeName = requestAttribute.Response.FullName,
|
||||||
Mandatory = context.ControllerMessageContext.Properties.Persistent.GetValueOrDefault(true)
|
Mandatory = context.MessageContext.Properties.Persistent.GetValueOrDefault(true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,8 +206,8 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var messageContext = context.ControllerMessageContext;
|
var messageContext = context.MessageContext;
|
||||||
if (messageContext == null || !messageContext.Get(ContextItems.FlowContext, out flowContext))
|
if (messageContext == null || !messageContext.TryGet<FlowMessageContextPayload>(out var flowPayload))
|
||||||
{
|
{
|
||||||
flowContext = new FlowContext
|
flowContext = new FlowContext
|
||||||
{
|
{
|
||||||
@ -218,6 +218,8 @@ namespace Tapeti.Flow.Default
|
|||||||
// in the messageContext as the yield point is the last to execute.
|
// in the messageContext as the yield point is the last to execute.
|
||||||
disposeFlowContext = true;
|
disposeFlowContext = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
flowContext = flowPayload.FlowContext;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
33
Tapeti.Flow/FlowMessageContextPayload.cs
Normal file
33
Tapeti.Flow/FlowMessageContextPayload.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using Tapeti.Config;
|
||||||
|
using Tapeti.Flow.Default;
|
||||||
|
|
||||||
|
namespace Tapeti.Flow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information about the flow for the current message. For internal use.
|
||||||
|
/// </summary>
|
||||||
|
internal class FlowMessageContextPayload : IMessageContextPayload, IDisposable
|
||||||
|
{
|
||||||
|
public FlowContext FlowContext { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the current message handler is the last one to be called before a
|
||||||
|
/// parallel flow is done and the convergeMethod will be called.
|
||||||
|
/// Temporarily disables storing the flow state.
|
||||||
|
/// </summary>
|
||||||
|
public bool FlowIsConverging { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public FlowMessageContextPayload(FlowContext flowContext)
|
||||||
|
{
|
||||||
|
FlowContext = flowContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
FlowContext?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,9 +29,9 @@ namespace Tapeti.Flow
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Access to the controller message context if this is a continuated flow.
|
/// Access to the message context if this is a continuated flow.
|
||||||
/// Will be null when in a starting flow.
|
/// Will be null when in a starting flow.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IControllerMessageContext ControllerMessageContext { get; }
|
IMessageContext MessageContext { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
using Tapeti.Config;
|
using Tapeti.Config;
|
||||||
using ISerilogLogger = Serilog.ILogger;
|
using ISerilogLogger = Serilog.ILogger;
|
||||||
|
|
||||||
@ -12,6 +13,21 @@ namespace Tapeti.Serilog
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TapetiSeriLogger: IBindingLogger
|
public class TapetiSeriLogger: IBindingLogger
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the Tapeti ILogger interface for Serilog output. This version
|
||||||
|
/// includes the message body and information if available when an error occurs.
|
||||||
|
/// </summary>
|
||||||
|
public class WithMessageLogging : TapetiSeriLogger
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WithMessageLogging(ISerilogLogger seriLogger) : base(seriLogger) { }
|
||||||
|
|
||||||
|
internal override bool IncludeMessageInfo() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private readonly ISerilogLogger seriLogger;
|
private readonly ISerilogLogger seriLogger;
|
||||||
|
|
||||||
|
|
||||||
@ -69,20 +85,38 @@ namespace Tapeti.Serilog
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ConsumeException(Exception exception, IMessageContext messageContext, ConsumeResult consumeResult)
|
public void ConsumeException(Exception exception, IMessageContext messageContext, ConsumeResult consumeResult)
|
||||||
{
|
{
|
||||||
|
var message = new StringBuilder("Tapeti: exception in message handler");
|
||||||
|
var messageParams = new List<object>();
|
||||||
|
|
||||||
var contextLogger = seriLogger
|
var contextLogger = seriLogger
|
||||||
.ForContext("consumeResult", consumeResult)
|
.ForContext("consumeResult", consumeResult)
|
||||||
.ForContext("exchange", messageContext.Exchange)
|
.ForContext("exchange", messageContext.Exchange)
|
||||||
.ForContext("queue", messageContext.Queue)
|
.ForContext("queue", messageContext.Queue)
|
||||||
.ForContext("routingKey", messageContext.RoutingKey);
|
.ForContext("routingKey", messageContext.RoutingKey);
|
||||||
|
|
||||||
if (messageContext is IControllerMessageContext controllerMessageContext)
|
if (messageContext.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||||
{
|
{
|
||||||
contextLogger = contextLogger
|
contextLogger = contextLogger
|
||||||
.ForContext("controller", controllerMessageContext.Binding.Controller.FullName)
|
.ForContext("controller", controllerPayload.Binding.Controller.FullName)
|
||||||
.ForContext("method", controllerMessageContext.Binding.Method.Name);
|
.ForContext("method", controllerPayload.Binding.Method.Name);
|
||||||
|
|
||||||
|
message.Append(" {controller}.{method}");
|
||||||
|
messageParams.Add(controllerPayload.Binding.Controller.FullName);
|
||||||
|
messageParams.Add(controllerPayload.Binding.Method.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
contextLogger.Error(exception, "Tapeti: exception in message handler");
|
if (IncludeMessageInfo())
|
||||||
|
{
|
||||||
|
message.Append(" on exchange {exchange}, queue {queue}, routingKey {routingKey}, replyTo {replyTo}, correlationId {correlationId} with body {body}");
|
||||||
|
messageParams.Add(messageContext.Exchange);
|
||||||
|
messageParams.Add(messageContext.Queue);
|
||||||
|
messageParams.Add(messageContext.RoutingKey);
|
||||||
|
messageParams.Add(messageContext.Properties.ReplyTo);
|
||||||
|
messageParams.Add(messageContext.Properties.CorrelationId);
|
||||||
|
messageParams.Add(messageContext.RawBody != null ? Encoding.UTF8.GetString(messageContext.RawBody) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
contextLogger.Error(exception, message.ToString(), messageParams.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -134,5 +168,7 @@ namespace Tapeti.Serilog
|
|||||||
else
|
else
|
||||||
seriLogger.Information("Tapeti: obsolete queue {queue} has been unbound but not yet deleted, {messageCount} messages remaining", queueName, messageCount);
|
seriLogger.Information("Tapeti: obsolete queue {queue} has been unbound but not yet deleted, {messageCount} messages remaining", queueName, messageCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal virtual bool IncludeMessageInfo() => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
Tapeti/Config/ControllerMessageContextPayload.cs
Normal file
32
Tapeti/Config/ControllerMessageContextPayload.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
namespace Tapeti.Config
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Extends the message context with information about the controller.
|
||||||
|
/// </summary>
|
||||||
|
public class ControllerMessageContextPayload : IMessageContextPayload
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An instance of the controller referenced by the binding. Note: can be null during Cleanup.
|
||||||
|
/// </summary>
|
||||||
|
public object Controller { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// Provides access to the binding which is currently processing the message.
|
||||||
|
/// </remarks>
|
||||||
|
public IControllerMethodBinding Binding { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs the payload to enrich the message context with information about the controller.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controller">An instance of the controller referenced by the binding</param>
|
||||||
|
/// <param name="binding">The binding which is currently processing the message</param>
|
||||||
|
public ControllerMessageContextPayload(object controller, IControllerMethodBinding binding)
|
||||||
|
{
|
||||||
|
Controller = controller;
|
||||||
|
Binding = binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ namespace Tapeti.Config
|
|||||||
/// Injects a value for a controller method parameter.
|
/// Injects a value for a controller method parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
public delegate object ValueFactory(IControllerMessageContext context);
|
public delegate object ValueFactory(IMessageContext context);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -19,7 +19,7 @@ namespace Tapeti.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
public delegate Task ResultHandler(IControllerMessageContext context, object value);
|
public delegate Task ResultHandler(IMessageContext context, object value);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -14,6 +14,6 @@ namespace Tapeti.Config
|
|||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <param name="consumeResult"></param>
|
/// <param name="consumeResult"></param>
|
||||||
/// <param name="next">Always call to allow the next in the chain to clean up</param>
|
/// <param name="next">Always call to allow the next in the chain to clean up</param>
|
||||||
Task Cleanup(IControllerMessageContext context, ConsumeResult consumeResult, Func<Task> next);
|
Task Cleanup(IMessageContext context, ConsumeResult consumeResult, Func<Task> next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,6 @@ namespace Tapeti.Config
|
|||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <param name="next"></param>
|
/// <param name="next"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task Filter(IControllerMessageContext context, Func<Task> next);
|
Task Filter(IMessageContext context, Func<Task> next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
namespace Tapeti.Config
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
|
||||||
/// Extends the message context with information about the controller.
|
|
||||||
/// </summary>
|
|
||||||
public interface IControllerMessageContext : IMessageContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An instance of the controller referenced by the binding. Note: is null during Cleanup.
|
|
||||||
/// </summary>
|
|
||||||
object Controller { get; }
|
|
||||||
|
|
||||||
|
|
||||||
/// <remarks>
|
|
||||||
/// Provides access to the binding which is currently processing the message.
|
|
||||||
/// </remarks>
|
|
||||||
new IControllerMethodBinding Binding { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,6 +14,6 @@ namespace Tapeti.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <param name="next">Call to pass the message to the next handler in the chain or call the controller method</param>
|
/// <param name="next">Call to pass the message to the next handler in the chain or call the controller method</param>
|
||||||
Task Handle(IControllerMessageContext context, Func<Task> next);
|
Task Handle(IMessageContext context, Func<Task> next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
// ReSharper disable UnusedMemberInSuper.Global - public API
|
||||||
|
|
||||||
namespace Tapeti.Config
|
namespace Tapeti.Config
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -27,6 +29,11 @@ namespace Tapeti.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string RoutingKey { get; }
|
string RoutingKey { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the raw body of the message.
|
||||||
|
/// </summary>
|
||||||
|
byte[] RawBody { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the decoded message instance.
|
/// Contains the decoded message instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -42,6 +49,36 @@ namespace Tapeti.Config
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
IBinding Binding { get; }
|
IBinding Binding { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores additional properties in the message context which can be passed between middleware stages.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only one instance of type T is stored, if Enrich was called before for this type an InvalidOperationException will be thrown.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="payload">A class implementing IMessageContextPayload</param>
|
||||||
|
void Store<T>(T payload) where T : IMessageContextPayload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stored a new payload, or updates an existing one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="onAdd">A method returning the new payload to be stored</param>
|
||||||
|
/// <param name="onUpdate">A method called when the payload exists</param>
|
||||||
|
/// <typeparam name="T">The payload type as passed to Enrich</typeparam>
|
||||||
|
void StoreOrUpdate<T>(Func<T> onAdd, Action<T> onUpdate) where T : IMessageContextPayload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the properties as previously stored with Enrich. Throws a KeyNotFoundException
|
||||||
|
/// if the payload is not stored in this message context.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The payload type as passed to Enrich</typeparam>
|
||||||
|
T Get<T>() where T : IMessageContextPayload;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true and the payload value if this message context was previously enriched with the payload T.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The payload type as passed to Enrich</typeparam>
|
||||||
|
bool TryGet<T>(out T payload) where T : IMessageContextPayload;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores a key-value pair in the context for passing information between the various
|
/// Stores a key-value pair in the context for passing information between the various
|
||||||
@ -49,6 +86,7 @@ namespace Tapeti.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">A unique key. It is recommended to prefix it with the package name which hosts the middleware to prevent conflicts</param>
|
/// <param name="key">A unique key. It is recommended to prefix it with the package name which hosts the middleware to prevent conflicts</param>
|
||||||
/// <param name="value">Will be disposed if the value implements IDisposable or IAsyncDisposable</param>
|
/// <param name="value">Will be disposed if the value implements IDisposable or IAsyncDisposable</param>
|
||||||
|
[Obsolete("For backwards compatibility only. Use Store<T> payload for typed properties instead")]
|
||||||
void Store(string key, object value);
|
void Store(string key, object value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -57,6 +95,18 @@ namespace Tapeti.Config
|
|||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns>True if the value was found, False otherwise</returns>
|
/// <returns>True if the value was found, False otherwise</returns>
|
||||||
|
[Obsolete("For backwards compatibility only. Use Get<T> payload overload for typed properties instead")]
|
||||||
bool Get<T>(string key, out T value) where T : class;
|
bool Get<T>(string key, out T value) where T : class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for additional properties added to the message context.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Descendants implementing IDisposable or IAsyncDisposable will be disposed along with the message context.
|
||||||
|
/// </remarks>
|
||||||
|
public interface IMessageContextPayload
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ namespace Tapeti.Connection
|
|||||||
|
|
||||||
return await DispatchMessage(message, new MessageContextData
|
return await DispatchMessage(message, new MessageContextData
|
||||||
{
|
{
|
||||||
|
RawBody = body,
|
||||||
Exchange = exchange,
|
Exchange = exchange,
|
||||||
RoutingKey = routingKey,
|
RoutingKey = routingKey,
|
||||||
Properties = properties
|
Properties = properties
|
||||||
@ -70,6 +71,7 @@ namespace Tapeti.Connection
|
|||||||
Queue = queueName,
|
Queue = queueName,
|
||||||
Exchange = exchange,
|
Exchange = exchange,
|
||||||
RoutingKey = routingKey,
|
RoutingKey = routingKey,
|
||||||
|
RawBody = body,
|
||||||
Message = message,
|
Message = message,
|
||||||
Properties = properties,
|
Properties = properties,
|
||||||
Binding = null
|
Binding = null
|
||||||
@ -112,6 +114,7 @@ namespace Tapeti.Connection
|
|||||||
Queue = queueName,
|
Queue = queueName,
|
||||||
Exchange = messageContextData.Exchange,
|
Exchange = messageContextData.Exchange,
|
||||||
RoutingKey = messageContextData.RoutingKey,
|
RoutingKey = messageContextData.RoutingKey,
|
||||||
|
RawBody = messageContextData.RawBody,
|
||||||
Message = message,
|
Message = message,
|
||||||
Properties = messageContextData.Properties,
|
Properties = messageContextData.Properties,
|
||||||
Binding = binding
|
Binding = binding
|
||||||
@ -174,6 +177,7 @@ namespace Tapeti.Connection
|
|||||||
|
|
||||||
private struct MessageContextData
|
private struct MessageContextData
|
||||||
{
|
{
|
||||||
|
public byte[] RawBody;
|
||||||
public string Exchange;
|
public string Exchange;
|
||||||
public string RoutingKey;
|
public string RoutingKey;
|
||||||
public IMessageProperties Properties;
|
public IMessageProperties Properties;
|
||||||
|
@ -11,6 +11,19 @@ namespace Tapeti.Default
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ConsoleLogger : IBindingLogger
|
public class ConsoleLogger : IBindingLogger
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default ILogger implementation for console applications. This version
|
||||||
|
/// includes the message body if available when an error occurs.
|
||||||
|
/// </summary>
|
||||||
|
public class WithMessageLogging : ConsoleLogger
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WithMessageLogging() : base() { }
|
||||||
|
|
||||||
|
internal override bool IncludeMessageBody() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Connect(IConnectContext connectContext)
|
public void Connect(IConnectContext connectContext)
|
||||||
{
|
{
|
||||||
@ -43,13 +56,19 @@ namespace Tapeti.Default
|
|||||||
Console.WriteLine($" Exchange : {messageContext.Exchange}");
|
Console.WriteLine($" Exchange : {messageContext.Exchange}");
|
||||||
Console.WriteLine($" Queue : {messageContext.Queue}");
|
Console.WriteLine($" Queue : {messageContext.Queue}");
|
||||||
Console.WriteLine($" RoutingKey : {messageContext.RoutingKey}");
|
Console.WriteLine($" RoutingKey : {messageContext.RoutingKey}");
|
||||||
|
Console.WriteLine($" ReplyTo : {messageContext.Properties.ReplyTo}");
|
||||||
|
Console.WriteLine($" CorrelationId : {messageContext.Properties.CorrelationId}");
|
||||||
|
|
||||||
if (messageContext is IControllerMessageContext controllerMessageContext)
|
if (messageContext.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||||
{
|
{
|
||||||
Console.WriteLine($" Controller : {controllerMessageContext.Binding.Controller.FullName}");
|
Console.WriteLine($" Controller : {controllerPayload.Binding.Controller.FullName}");
|
||||||
Console.WriteLine($" Method : {controllerMessageContext.Binding.Method.Name}");
|
Console.WriteLine($" Method : {controllerPayload.Binding.Method.Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IncludeMessageBody())
|
||||||
|
Console.WriteLine($" Body : {(messageContext.RawBody != null ? Encoding.UTF8.GetString(messageContext.RawBody) : "<null>")}");
|
||||||
|
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine(exception);
|
Console.WriteLine(exception);
|
||||||
}
|
}
|
||||||
@ -102,5 +121,7 @@ namespace Tapeti.Default
|
|||||||
? $"[Tapeti] Obsolete queue was deleted: {queueName}"
|
? $"[Tapeti] Obsolete queue was deleted: {queueName}"
|
||||||
: $"[Tapeti] Obsolete queue bindings removed: {queueName}, {messageCount} messages remaining");
|
: $"[Tapeti] Obsolete queue bindings removed: {queueName}, {messageCount} messages remaining");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal virtual bool IncludeMessageBody() => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Tapeti.Config;
|
|
||||||
|
|
||||||
namespace Tapeti.Default
|
|
||||||
{
|
|
||||||
internal class ControllerMessageContext : IControllerMessageContext
|
|
||||||
{
|
|
||||||
private readonly IMessageContext decoratedContext;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object Controller { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ITapetiConfig Config => decoratedContext.Config;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Queue => decoratedContext.Queue;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Exchange => decoratedContext.Exchange;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string RoutingKey => decoratedContext.RoutingKey;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object Message => decoratedContext.Message;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IMessageProperties Properties => decoratedContext.Properties;
|
|
||||||
|
|
||||||
|
|
||||||
IBinding IMessageContext.Binding => decoratedContext.Binding;
|
|
||||||
IControllerMethodBinding IControllerMessageContext.Binding => decoratedContext.Binding as IControllerMethodBinding;
|
|
||||||
|
|
||||||
|
|
||||||
public ControllerMessageContext(IMessageContext decoratedContext)
|
|
||||||
{
|
|
||||||
this.decoratedContext = decoratedContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Do not call decoratedContext.Dispose - by design
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
// Do not call decoratedContext.DisposeAsync - by design
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Store(string key, object value)
|
|
||||||
{
|
|
||||||
decoratedContext.Store(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool Get<T>(string key, out T value) where T : class
|
|
||||||
{
|
|
||||||
return decoratedContext.Get(key, out value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -160,39 +160,30 @@ namespace Tapeti.Default
|
|||||||
public async Task Invoke(IMessageContext context)
|
public async Task Invoke(IMessageContext context)
|
||||||
{
|
{
|
||||||
var controller = dependencyResolver.Resolve(bindingInfo.ControllerType);
|
var controller = dependencyResolver.Resolve(bindingInfo.ControllerType);
|
||||||
|
context.Store(new ControllerMessageContextPayload(controller, context.Binding as IControllerMethodBinding));
|
||||||
|
|
||||||
await using var controllerContext = new ControllerMessageContext(context)
|
if (!await FilterAllowed(context))
|
||||||
{
|
|
||||||
Controller = controller
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!await FilterAllowed(controllerContext))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
await MiddlewareHelper.GoAsync(
|
await MiddlewareHelper.GoAsync(
|
||||||
bindingInfo.MessageMiddleware,
|
bindingInfo.MessageMiddleware,
|
||||||
async (handler, next) => await handler.Handle(controllerContext, next),
|
async (handler, next) => await handler.Handle(context, next),
|
||||||
async () => await messageHandler(controllerContext));
|
async () => await messageHandler(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task Cleanup(IMessageContext context, ConsumeResult consumeResult)
|
public async Task Cleanup(IMessageContext context, ConsumeResult consumeResult)
|
||||||
{
|
{
|
||||||
await using var controllerContext = new ControllerMessageContext(context)
|
|
||||||
{
|
|
||||||
Controller = null
|
|
||||||
};
|
|
||||||
|
|
||||||
await MiddlewareHelper.GoAsync(
|
await MiddlewareHelper.GoAsync(
|
||||||
bindingInfo.CleanupMiddleware,
|
bindingInfo.CleanupMiddleware,
|
||||||
async (handler, next) => await handler.Cleanup(controllerContext, consumeResult, next),
|
async (handler, next) => await handler.Cleanup(context, consumeResult, next),
|
||||||
() => Task.CompletedTask);
|
() => Task.CompletedTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task<bool> FilterAllowed(IControllerMessageContext context)
|
private async Task<bool> FilterAllowed(IMessageContext context)
|
||||||
{
|
{
|
||||||
var allowed = false;
|
var allowed = false;
|
||||||
await MiddlewareHelper.GoAsync(
|
await MiddlewareHelper.GoAsync(
|
||||||
@ -208,7 +199,7 @@ namespace Tapeti.Default
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private delegate Task MessageHandlerFunc(IControllerMessageContext context);
|
private delegate Task MessageHandlerFunc(IMessageContext context);
|
||||||
|
|
||||||
|
|
||||||
private MessageHandlerFunc WrapMethod(MethodInfo method, IEnumerable<ValueFactory> parameterFactories, ResultHandler resultHandler)
|
private MessageHandlerFunc WrapMethod(MethodInfo method, IEnumerable<ValueFactory> parameterFactories, ResultHandler resultHandler)
|
||||||
@ -233,9 +224,10 @@ namespace Tapeti.Default
|
|||||||
{
|
{
|
||||||
return context =>
|
return context =>
|
||||||
{
|
{
|
||||||
|
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
var result = method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||||
return resultHandler(context, result);
|
return resultHandler(context, result);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -250,9 +242,10 @@ namespace Tapeti.Default
|
|||||||
{
|
{
|
||||||
return context =>
|
return context =>
|
||||||
{
|
{
|
||||||
|
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -268,9 +261,10 @@ namespace Tapeti.Default
|
|||||||
{
|
{
|
||||||
return context =>
|
return context =>
|
||||||
{
|
{
|
||||||
|
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (Task) method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
return (Task) method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -285,9 +279,10 @@ namespace Tapeti.Default
|
|||||||
{
|
{
|
||||||
return context =>
|
return context =>
|
||||||
{
|
{
|
||||||
|
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (Task<object>)method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
return (Task<object>)method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -302,9 +297,10 @@ namespace Tapeti.Default
|
|||||||
{
|
{
|
||||||
return context =>
|
return context =>
|
||||||
{
|
{
|
||||||
|
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Task.FromResult(method.Invoke(context.Controller, parameterFactories.Select(p => p(context)).ToArray()));
|
return Task.FromResult(method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray()));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@ namespace Tapeti.Default
|
|||||||
{
|
{
|
||||||
internal class MessageContext : IMessageContext
|
internal class MessageContext : IMessageContext
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, object> items = new();
|
private readonly Dictionary<Type, IMessageContextPayload> payloads = new();
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -22,6 +22,9 @@ namespace Tapeti.Default
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string RoutingKey { get; set; }
|
public string RoutingKey { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte[] RawBody { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object Message { get; set; }
|
public object Message { get; set; }
|
||||||
|
|
||||||
@ -32,20 +35,51 @@ namespace Tapeti.Default
|
|||||||
public IBinding Binding { get; set; }
|
public IBinding Binding { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public void Store<T>(T payload) where T : IMessageContextPayload
|
||||||
|
{
|
||||||
|
payloads.Add(typeof(T), payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StoreOrUpdate<T>(Func<T> onAdd, Action<T> onUpdate) where T : IMessageContextPayload
|
||||||
|
{
|
||||||
|
if (payloads.TryGetValue(typeof(T), out var payload))
|
||||||
|
onUpdate((T)payload);
|
||||||
|
else
|
||||||
|
payloads.Add(typeof(T), onAdd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get<T>() where T : IMessageContextPayload
|
||||||
|
{
|
||||||
|
return (T)payloads[typeof(T)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet<T>(out T payload) where T : IMessageContextPayload
|
||||||
|
{
|
||||||
|
if (payloads.TryGetValue(typeof(T), out var payloadValue))
|
||||||
|
{
|
||||||
|
payload = (T)payloadValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var item in items.Values)
|
foreach (var payload in payloads.Values)
|
||||||
(item as IDisposable)?.Dispose();
|
(payload as IDisposable)?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
foreach (var item in items.Values)
|
foreach (var payload in payloads.Values)
|
||||||
{
|
{
|
||||||
if (item is IAsyncDisposable asyncDisposable)
|
if (payload is IAsyncDisposable asyncDisposable)
|
||||||
await asyncDisposable.DisposeAsync();
|
await asyncDisposable.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,21 +89,66 @@ namespace Tapeti.Default
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Store(string key, object value)
|
public void Store(string key, object value)
|
||||||
{
|
{
|
||||||
items.Add(key, value);
|
StoreOrUpdate(
|
||||||
|
() => new KeyValuePayload(key, value),
|
||||||
|
payload => payload.Add(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Get<T>(string key, out T value) where T : class
|
public bool Get<T>(string key, out T value) where T : class
|
||||||
{
|
{
|
||||||
if (!items.TryGetValue(key, out var objectValue))
|
if (!TryGet<KeyValuePayload>(out var payload) ||
|
||||||
|
!payload.TryGetValue(key, out var objectValue))
|
||||||
{
|
{
|
||||||
value = default(T);
|
value = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = (T)objectValue;
|
value = (T)objectValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public class KeyValuePayload : IMessageContextPayload, IDisposable, IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, object> items = new();
|
||||||
|
|
||||||
|
|
||||||
|
public KeyValuePayload(string key, object value)
|
||||||
|
{
|
||||||
|
Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Add(string key, object value)
|
||||||
|
{
|
||||||
|
items.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool TryGetValue(string key, out object value)
|
||||||
|
{
|
||||||
|
return items.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var item in items.Values)
|
||||||
|
(item as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
foreach (var item in items.Values)
|
||||||
|
{
|
||||||
|
if (item is IAsyncDisposable asyncDisposable)
|
||||||
|
await asyncDisposable.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user