Added ValueTask support
- This is a breaking change for custom middleware implementations Added validation for return type handling - This may be breaking for incorrect implementations, but highly unlikely
This commit is contained in:
parent
b816e56018
commit
165680fd38
@ -9,8 +9,7 @@ namespace _03_FlowRequestResponse
|
||||
public class ReceivingMessageController
|
||||
{
|
||||
// No publisher required, responses can simply be returned
|
||||
#pragma warning disable CA1822 // Mark members as static - not supported yet by Tapeti
|
||||
public async Task<QuoteResponseMessage> HandleQuoteRequest(QuoteRequestMessage message)
|
||||
public static async Task<QuoteResponseMessage> HandleQuoteRequest(QuoteRequestMessage message)
|
||||
{
|
||||
var quote = message.Amount switch
|
||||
{
|
||||
@ -29,6 +28,5 @@ namespace _03_FlowRequestResponse
|
||||
Quote = quote
|
||||
};
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,11 @@ namespace _05_SpeedTest
|
||||
}
|
||||
|
||||
|
||||
#pragma warning disable IDE0060 // Remove unused parameter
|
||||
public void HandleSpeedTestMessage(SpeedTestMessage message)
|
||||
{
|
||||
messageCounter.Add();
|
||||
}
|
||||
#pragma warning restore IDE0060
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,7 @@ namespace _06_StatelessRequestResponse
|
||||
public class ReceivingMessageController
|
||||
{
|
||||
// No publisher required, responses can simply be returned
|
||||
#pragma warning disable CA1822 // Mark members as static - not supported yet by Tapeti
|
||||
public QuoteResponseMessage HandleQuoteRequest(QuoteRequestMessage message)
|
||||
public static QuoteResponseMessage HandleQuoteRequest(QuoteRequestMessage message)
|
||||
{
|
||||
var quote = message.Amount switch
|
||||
{
|
||||
@ -25,6 +24,5 @@ namespace _06_StatelessRequestResponse
|
||||
Quote = quote
|
||||
};
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ namespace Tapeti.DataAnnotations
|
||||
internal class DataAnnotationsMessageMiddleware : IMessageMiddleware
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task Handle(IMessageContext context, Func<Task> next)
|
||||
public ValueTask Handle(IMessageContext context, Func<ValueTask> next)
|
||||
{
|
||||
var validationContext = new ValidationContext(context.Message);
|
||||
Validator.ValidateObject(context.Message, validationContext, true);
|
||||
|
||||
await next();
|
||||
return next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ namespace Tapeti.DataAnnotations
|
||||
internal class DataAnnotationsPublishMiddleware : IPublishMiddleware
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task Handle(IPublishContext context, Func<Task> next)
|
||||
public ValueTask Handle(IPublishContext context, Func<ValueTask> next)
|
||||
{
|
||||
var validationContext = new ValidationContext(context.Message);
|
||||
Validator.ValidateObject(context.Message, validationContext, true);
|
||||
|
||||
await next();
|
||||
return next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,10 @@ using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
// Neither of these are available in language version 7 required for .NET Standard 2.0
|
||||
// ReSharper disable ConvertToUsingDeclaration
|
||||
// ReSharper disable UseAwaitUsing
|
||||
|
||||
namespace Tapeti.Flow.SQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@ -37,7 +41,7 @@ namespace Tapeti.Flow.SQL
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<FlowRecord<T>>> GetStates<T>()
|
||||
public async ValueTask<IEnumerable<FlowRecord<T>>> GetStates<T>()
|
||||
{
|
||||
return await SqlRetryHelper.Execute(async () =>
|
||||
{
|
||||
@ -64,7 +68,7 @@ namespace Tapeti.Flow.SQL
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CreateState<T>(Guid flowID, T state, DateTime timestamp)
|
||||
public async ValueTask CreateState<T>(Guid flowID, T state, DateTime timestamp)
|
||||
{
|
||||
await SqlRetryHelper.Execute(async () =>
|
||||
{
|
||||
@ -88,7 +92,7 @@ namespace Tapeti.Flow.SQL
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateState<T>(Guid flowID, T state)
|
||||
public async ValueTask UpdateState<T>(Guid flowID, T state)
|
||||
{
|
||||
await SqlRetryHelper.Execute(async () =>
|
||||
{
|
||||
@ -108,7 +112,7 @@ namespace Tapeti.Flow.SQL
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteState(Guid flowID)
|
||||
public async ValueTask DeleteState(Guid flowID)
|
||||
{
|
||||
await SqlRetryHelper.Execute(async () =>
|
||||
{
|
||||
|
@ -17,6 +17,11 @@
|
||||
<NoWarn>1701;1702</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'!='netstandard2.0'">
|
||||
<!-- Suppress 'using statement can be simplified' which requires language version 8 not available in .NET Standard 2.0 -->
|
||||
<NoWarn>IDE0063</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="scripts\Flow table.sql" />
|
||||
</ItemGroup>
|
||||
|
@ -31,6 +31,9 @@ namespace Tapeti.Flow.Default
|
||||
if (continuationAttribute == null)
|
||||
return;
|
||||
|
||||
if (context.Method.IsStatic)
|
||||
throw new ArgumentException($"Continuation attribute is not valid on static methods in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}");
|
||||
|
||||
context.SetBindingTargetMode(BindingTargetMode.Direct);
|
||||
context.Use(new FlowContinuationMiddleware());
|
||||
|
||||
@ -52,7 +55,7 @@ namespace Tapeti.Flow.Default
|
||||
context.Result.SetHandler((messageContext, value) => HandleParallelResponse(messageContext));
|
||||
}
|
||||
else
|
||||
throw new ArgumentException($"Result type must be IYieldPoint, Task or void in controller {context. Method.DeclaringType?.FullName}, method {context.Method.Name}");
|
||||
throw new ArgumentException($"Result type must be IYieldPoint, Task or void in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}");
|
||||
|
||||
|
||||
foreach (var parameter in context.Parameters.Where(p => !p.HasBinding && p.Info.ParameterType == typeof(IFlowParallelRequest)))
|
||||
@ -62,34 +65,53 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
private static void RegisterYieldPointResult(IControllerBindingContext context)
|
||||
{
|
||||
if (!context.Result.Info.ParameterType.IsTypeOrTaskOf(typeof(IYieldPoint), out var isTaskOf))
|
||||
if (!context.Result.Info.ParameterType.IsTypeOrTaskOf(typeof(IYieldPoint), out var taskType))
|
||||
return;
|
||||
|
||||
if (isTaskOf)
|
||||
if (context.Method.IsStatic)
|
||||
throw new ArgumentException($"Yield points are not valid on static methods in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}");
|
||||
|
||||
switch (taskType)
|
||||
{
|
||||
context.Result.SetHandler(async (messageContext, value) =>
|
||||
{
|
||||
var yieldPoint = await (Task<IYieldPoint>)value;
|
||||
if (yieldPoint != null)
|
||||
await HandleYieldPoint(messageContext, yieldPoint);
|
||||
});
|
||||
case TaskType.None:
|
||||
context.Result.SetHandler((messageContext, value) => HandleYieldPoint(messageContext, (IYieldPoint)value));
|
||||
break;
|
||||
|
||||
case TaskType.Task:
|
||||
context.Result.SetHandler(async (messageContext, value) =>
|
||||
{
|
||||
var yieldPoint = await (Task<IYieldPoint>)value;
|
||||
if (yieldPoint != null)
|
||||
await HandleYieldPoint(messageContext, yieldPoint);
|
||||
});
|
||||
break;
|
||||
|
||||
case TaskType.ValueTask:
|
||||
context.Result.SetHandler(async (messageContext, value) =>
|
||||
{
|
||||
var yieldPoint = await (ValueTask<IYieldPoint>)value;
|
||||
if (yieldPoint != null)
|
||||
await HandleYieldPoint(messageContext, yieldPoint);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
else
|
||||
context.Result.SetHandler((messageContext, value) => HandleYieldPoint(messageContext, (IYieldPoint)value));
|
||||
}
|
||||
|
||||
|
||||
private static Task HandleYieldPoint(IMessageContext context, IYieldPoint yieldPoint)
|
||||
private static ValueTask HandleYieldPoint(IMessageContext context, IYieldPoint yieldPoint)
|
||||
{
|
||||
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
||||
return flowHandler.Execute(new FlowHandlerContext(context), yieldPoint);
|
||||
}
|
||||
|
||||
|
||||
private static Task HandleParallelResponse(IMessageContext context)
|
||||
private static ValueTask HandleParallelResponse(IMessageContext context)
|
||||
{
|
||||
if (context.TryGet<FlowMessageContextPayload>(out var flowPayload) && flowPayload.FlowIsConverging)
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
|
||||
var flowHandler = context.Config.DependencyResolver.Resolve<IFlowHandler>();
|
||||
return flowHandler.Execute(new FlowHandlerContext(context), new DelegateYieldPoint(async flowContext =>
|
||||
|
@ -17,7 +17,7 @@ namespace Tapeti.Flow.Default
|
||||
private int deleteCalled;
|
||||
|
||||
|
||||
public async Task Store(bool persistent)
|
||||
public ValueTask Store(bool persistent)
|
||||
{
|
||||
storeCalled++;
|
||||
|
||||
@ -26,15 +26,13 @@ namespace Tapeti.Flow.Default
|
||||
if (FlowStateLock == null) throw new ArgumentNullException(nameof(FlowStateLock));
|
||||
|
||||
FlowState.Data = Newtonsoft.Json.JsonConvert.SerializeObject(HandlerContext.Controller);
|
||||
await FlowStateLock.StoreFlowState(FlowState, persistent);
|
||||
return FlowStateLock.StoreFlowState(FlowState, persistent);
|
||||
}
|
||||
|
||||
public async Task Delete()
|
||||
public ValueTask Delete()
|
||||
{
|
||||
deleteCalled++;
|
||||
|
||||
if (FlowStateLock != null)
|
||||
await FlowStateLock.DeleteFlowState();
|
||||
return FlowStateLock?.DeleteFlowState() ?? default;
|
||||
}
|
||||
|
||||
public bool IsStoredOrDeleted()
|
||||
|
@ -11,7 +11,7 @@ namespace Tapeti.Flow.Default
|
||||
/// </summary>
|
||||
internal class FlowContinuationMiddleware : IControllerFilterMiddleware, IControllerMessageMiddleware, IControllerCleanupMiddleware
|
||||
{
|
||||
public async Task Filter(IMessageContext context, Func<Task> next)
|
||||
public async ValueTask Filter(IMessageContext context, Func<ValueTask> next)
|
||||
{
|
||||
if (!context.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||
return;
|
||||
@ -27,7 +27,7 @@ namespace Tapeti.Flow.Default
|
||||
}
|
||||
|
||||
|
||||
public async Task Handle(IMessageContext context, Func<Task> next)
|
||||
public async ValueTask Handle(IMessageContext context, Func<ValueTask> next)
|
||||
{
|
||||
if (!context.TryGet<ControllerMessageContextPayload>(out var controllerPayload))
|
||||
return;
|
||||
@ -53,7 +53,7 @@ namespace Tapeti.Flow.Default
|
||||
}
|
||||
|
||||
|
||||
public async Task Cleanup(IMessageContext context, ConsumeResult consumeResult, Func<Task> next)
|
||||
public async ValueTask Cleanup(IMessageContext context, ConsumeResult consumeResult, Func<ValueTask> next)
|
||||
{
|
||||
await next();
|
||||
|
||||
@ -82,7 +82,7 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
|
||||
|
||||
private static async Task<FlowContext> EnrichWithFlowContext(IMessageContext context)
|
||||
private static async ValueTask<FlowContext> EnrichWithFlowContext(IMessageContext context)
|
||||
{
|
||||
if (context.TryGet<FlowMessageContextPayload>(out var flowPayload))
|
||||
return flowPayload.FlowContext;
|
||||
|
@ -197,7 +197,7 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Execute(IFlowHandlerContext context, IYieldPoint yieldPoint)
|
||||
public async ValueTask Execute(IFlowHandlerContext context, IYieldPoint yieldPoint)
|
||||
{
|
||||
if (!(yieldPoint is DelegateYieldPoint executableYieldPoint))
|
||||
throw new YieldPointException($"Yield point is required in controller {context.Controller.GetType().Name} for method {context.Method.Name}");
|
||||
@ -254,7 +254,7 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Converge(IFlowHandlerContext context)
|
||||
public ValueTask Converge(IFlowHandlerContext context)
|
||||
{
|
||||
return Execute(context, new DelegateYieldPoint(flowContext =>
|
||||
Converge(flowContext, flowContext.ContinuationMetadata.ConvergeMethodName, flowContext.ContinuationMetadata.ConvergeMethodSync)));
|
||||
|
@ -51,7 +51,7 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Load()
|
||||
public async ValueTask Load()
|
||||
{
|
||||
if (inUse)
|
||||
throw new InvalidOperationException("Can only load the saved state once.");
|
||||
@ -114,17 +114,17 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Guid?> FindFlowID(Guid continuationID)
|
||||
public ValueTask<Guid?> FindFlowID(Guid continuationID)
|
||||
{
|
||||
if (!loaded)
|
||||
throw new InvalidOperationException("Flow store is not yet loaded.");
|
||||
|
||||
return Task.FromResult(continuationLookup.TryGetValue(continuationID, out var result) ? result : (Guid?)null);
|
||||
return new ValueTask<Guid?>(continuationLookup.TryGetValue(continuationID, out var result) ? result : (Guid?)null);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IFlowStateLock> LockFlowState(Guid flowID)
|
||||
public async ValueTask<IFlowStateLock> LockFlowState(Guid flowID)
|
||||
{
|
||||
if (!loaded)
|
||||
throw new InvalidOperationException("Flow store should be loaded before storing flows.");
|
||||
@ -137,14 +137,14 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IEnumerable<ActiveFlow>> GetActiveFlows(TimeSpan minimumAge)
|
||||
public ValueTask<IEnumerable<ActiveFlow>> GetActiveFlows(TimeSpan minimumAge)
|
||||
{
|
||||
var maximumDateTime = DateTime.UtcNow - minimumAge;
|
||||
|
||||
return Task.FromResult(flowStates
|
||||
return new ValueTask<IEnumerable<ActiveFlow>>(flowStates
|
||||
.Where(p => p.Value.CreationTime <= maximumDateTime)
|
||||
.Select(p => new ActiveFlow(p.Key, p.Value.CreationTime))
|
||||
.ToArray() as IEnumerable<ActiveFlow>);
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
|
||||
@ -173,15 +173,15 @@ namespace Tapeti.Flow.Default
|
||||
l?.Dispose();
|
||||
}
|
||||
|
||||
public Task<FlowState> GetFlowState()
|
||||
public ValueTask<FlowState> GetFlowState()
|
||||
{
|
||||
if (flowLock == null)
|
||||
throw new ObjectDisposedException("FlowStateLock");
|
||||
|
||||
return Task.FromResult(cachedFlowState?.FlowState?.Clone());
|
||||
return new ValueTask<FlowState>(cachedFlowState?.FlowState?.Clone());
|
||||
}
|
||||
|
||||
public async Task StoreFlowState(FlowState newFlowState, bool persistent)
|
||||
public async ValueTask StoreFlowState(FlowState newFlowState, bool persistent)
|
||||
{
|
||||
if (flowLock == null)
|
||||
throw new ObjectDisposedException("FlowStateLock");
|
||||
@ -227,7 +227,7 @@ namespace Tapeti.Flow.Default
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteFlowState()
|
||||
public async ValueTask DeleteFlowState()
|
||||
{
|
||||
if (flowLock == null)
|
||||
throw new ObjectDisposedException("FlowStateLock");
|
||||
|
@ -11,27 +11,27 @@ namespace Tapeti.Flow.Default
|
||||
/// </summary>
|
||||
public class NonPersistentFlowRepository : IFlowRepository
|
||||
{
|
||||
Task<IEnumerable<FlowRecord<T>>> IFlowRepository.GetStates<T>()
|
||||
ValueTask<IEnumerable<FlowRecord<T>>> IFlowRepository.GetStates<T>()
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<FlowRecord<T>>());
|
||||
return new ValueTask<IEnumerable<FlowRecord<T>>>(Enumerable.Empty<FlowRecord<T>>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateState<T>(Guid flowID, T state, DateTime timestamp)
|
||||
public ValueTask CreateState<T>(Guid flowID, T state, DateTime timestamp)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task UpdateState<T>(Guid flowID, T state)
|
||||
public ValueTask UpdateState<T>(Guid flowID, T state)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteState(Guid flowID)
|
||||
public ValueTask DeleteState(Guid flowID)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ namespace Tapeti.Flow
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="yieldPoint"></param>
|
||||
Task Execute(IFlowHandlerContext context, IYieldPoint yieldPoint);
|
||||
ValueTask Execute(IFlowHandlerContext context, IYieldPoint yieldPoint);
|
||||
|
||||
|
||||
/// <summary>
|
||||
@ -120,7 +120,7 @@ namespace Tapeti.Flow
|
||||
/// <summary>
|
||||
/// Calls the converge method for a parallel flow.
|
||||
/// </summary>
|
||||
Task Converge(IFlowHandlerContext context);
|
||||
ValueTask Converge(IFlowHandlerContext context);
|
||||
}
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ namespace Tapeti.Flow
|
||||
/// Load the previously persisted flow states.
|
||||
/// </summary>
|
||||
/// <returns>A list of flow states, where the key is the unique Flow ID and the value is the deserialized T.</returns>
|
||||
Task<IEnumerable<FlowRecord<T>>> GetStates<T>();
|
||||
ValueTask<IEnumerable<FlowRecord<T>>> GetStates<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Stores a new flow state. Guaranteed to be run in a lock for the specified flow ID.
|
||||
@ -22,20 +22,20 @@ namespace Tapeti.Flow
|
||||
/// <param name="state">The flow state to be stored.</param>
|
||||
/// <param name="timestamp">The time when the flow was initially created.</param>
|
||||
/// <returns></returns>
|
||||
Task CreateState<T>(Guid flowID, T state, DateTime timestamp);
|
||||
ValueTask CreateState<T>(Guid flowID, T state, DateTime timestamp);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing flow state. Guaranteed to be run in a lock for the specified flow ID.
|
||||
/// </summary>
|
||||
/// <param name="flowID">The unique ID of the flow.</param>
|
||||
/// <param name="state">The flow state to be stored.</param>
|
||||
Task UpdateState<T>(Guid flowID, T state);
|
||||
ValueTask UpdateState<T>(Guid flowID, T state);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a flow state. Guaranteed to be run in a lock for the specified flow ID.
|
||||
/// </summary>
|
||||
/// <param name="flowID">The unique ID of the flow.</param>
|
||||
Task DeleteState(Guid flowID);
|
||||
ValueTask DeleteState(Guid flowID);
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,19 +17,19 @@ namespace Tapeti.Flow
|
||||
/// If using an IFlowRepository that requires an update (such as creating tables) make
|
||||
/// sure it is called before calling Load.
|
||||
/// </summary>
|
||||
Task Load();
|
||||
ValueTask Load();
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the FlowID corresponding to a ContinuationID. For internal use.
|
||||
/// </summary>
|
||||
/// <param name="continuationID"></param>
|
||||
Task<Guid?> FindFlowID(Guid continuationID);
|
||||
ValueTask<Guid?> FindFlowID(Guid continuationID);
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a lock on the flow with the specified FlowID.
|
||||
/// </summary>
|
||||
/// <param name="flowID"></param>
|
||||
Task<IFlowStateLock> LockFlowState(Guid flowID);
|
||||
ValueTask<IFlowStateLock> LockFlowState(Guid flowID);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the currently active flows.
|
||||
@ -38,7 +38,7 @@ namespace Tapeti.Flow
|
||||
/// This is intended for monitoring purposes and should be treated as a snapshot.
|
||||
/// </remarks>
|
||||
/// <param name="minimumAge">The minimum age of the flow before it is included in the result. Set to TimeSpan.Zero to return all active flows.</param>
|
||||
Task<IEnumerable<ActiveFlow>> GetActiveFlows(TimeSpan minimumAge);
|
||||
ValueTask<IEnumerable<ActiveFlow>> GetActiveFlows(TimeSpan minimumAge);
|
||||
}
|
||||
|
||||
|
||||
@ -56,19 +56,19 @@ namespace Tapeti.Flow
|
||||
/// <summary>
|
||||
/// Acquires a copy of the flow state.
|
||||
/// </summary>
|
||||
Task<FlowState> GetFlowState();
|
||||
ValueTask<FlowState> GetFlowState();
|
||||
|
||||
/// <summary>
|
||||
/// Stores the new flow state.
|
||||
/// </summary>
|
||||
/// <param name="flowState"></param>
|
||||
/// <param name="persistent"></param>
|
||||
Task StoreFlowState(FlowState flowState, bool persistent);
|
||||
ValueTask StoreFlowState(FlowState flowState, bool persistent);
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the flow state corresponding to this Flow ID.
|
||||
/// </summary>
|
||||
Task DeleteFlowState();
|
||||
ValueTask DeleteFlowState();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
@ -18,7 +18,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'!='netstandard2.0'">
|
||||
<!-- Supress 'Use switch expression' which requires language version 8 not available in .NET Standard 2.0 -->
|
||||
<!-- Suppress 'Use switch expression' which requires language version 8 not available in .NET Standard 2.0 -->
|
||||
<NoWarn>IDE0066</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -29,7 +29,7 @@ namespace Tapeti.Serilog.Middleware
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Handle(IMessageContext context, Func<Task> next)
|
||||
public async ValueTask Handle(IMessageContext context, Func<ValueTask> next)
|
||||
{
|
||||
var logger = context.Config.DependencyResolver.Resolve<global::Serilog.ILogger>();
|
||||
|
||||
@ -41,6 +41,7 @@ namespace Tapeti.Serilog.Middleware
|
||||
|
||||
await next();
|
||||
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace Tapeti.Config
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="value"></param>
|
||||
public delegate Task ResultHandler(IMessageContext context, object value);
|
||||
public delegate ValueTask ResultHandler(IMessageContext context, object value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
@ -14,6 +14,6 @@ namespace Tapeti.Config
|
||||
/// <param name="context"></param>
|
||||
/// <param name="consumeResult"></param>
|
||||
/// <param name="next">Always call to allow the next in the chain to clean up</param>
|
||||
Task Cleanup(IMessageContext context, ConsumeResult consumeResult, Func<Task> next);
|
||||
ValueTask Cleanup(IMessageContext context, ConsumeResult consumeResult, Func<ValueTask> next);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,6 @@ namespace Tapeti.Config
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next"></param>
|
||||
/// <returns></returns>
|
||||
Task Filter(IMessageContext context, Func<Task> next);
|
||||
ValueTask Filter(IMessageContext context, Func<ValueTask> next);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,6 @@ namespace Tapeti.Config
|
||||
/// </summary>
|
||||
/// <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>
|
||||
Task Handle(IMessageContext context, Func<Task> next);
|
||||
ValueTask Handle(IMessageContext context, Func<ValueTask> next);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,6 @@ namespace Tapeti.Config
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next">Call to pass the message to the next handler in the chain</param>
|
||||
Task Handle(IMessageContext context, Func<Task> next);
|
||||
ValueTask Handle(IMessageContext context, Func<ValueTask> next);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,6 @@ namespace Tapeti.Config
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="next">Call to pass the message to the next handler in the chain</param>
|
||||
Task Handle(IPublishContext context, Func<Task> next);
|
||||
ValueTask Handle(IPublishContext context, Func<ValueTask> next);
|
||||
}
|
||||
}
|
||||
|
@ -80,14 +80,13 @@ namespace Tapeti.Connection
|
||||
|
||||
cancellationToken = initializeCancellationTokenSource.Token;
|
||||
|
||||
// ReSharper disable once MethodSupportsCancellation
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await ApplyBindings(cancellationToken);
|
||||
|
||||
if (consuming && !cancellationToken.IsCancellationRequested)
|
||||
await ConsumeQueues(cancellationToken);
|
||||
});
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
||||
|
@ -159,7 +159,7 @@ namespace Tapeti.Default
|
||||
/// <inheritdoc />
|
||||
public async Task Invoke(IMessageContext context)
|
||||
{
|
||||
var controller = dependencyResolver.Resolve(bindingInfo.ControllerType);
|
||||
var controller = Method.IsStatic ? null : dependencyResolver.Resolve(bindingInfo.ControllerType);
|
||||
context.Store(new ControllerMessageContextPayload(controller, context.Binding as IControllerMethodBinding));
|
||||
|
||||
if (!await FilterAllowed(context))
|
||||
@ -179,7 +179,7 @@ namespace Tapeti.Default
|
||||
await MiddlewareHelper.GoAsync(
|
||||
bindingInfo.CleanupMiddleware,
|
||||
async (handler, next) => await handler.Cleanup(context, consumeResult, next),
|
||||
() => Task.CompletedTask);
|
||||
() => default);
|
||||
}
|
||||
|
||||
|
||||
@ -192,14 +192,14 @@ namespace Tapeti.Default
|
||||
() =>
|
||||
{
|
||||
allowed = true;
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
});
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
|
||||
private delegate Task MessageHandlerFunc(IMessageContext context);
|
||||
private delegate ValueTask MessageHandlerFunc(IMessageContext context);
|
||||
|
||||
|
||||
private MessageHandlerFunc WrapMethod(MethodInfo method, IEnumerable<ValueFactory> parameterFactories, ResultHandler resultHandler)
|
||||
@ -213,10 +213,11 @@ namespace Tapeti.Default
|
||||
if (method.ReturnType == typeof(Task))
|
||||
return WrapTaskMethod(method, parameterFactories);
|
||||
|
||||
if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
return WrapGenericTaskMethod(method, parameterFactories);
|
||||
if (method.ReturnType == typeof(ValueTask))
|
||||
return WrapValueTaskMethod(method, parameterFactories);
|
||||
|
||||
return WrapObjectMethod(method, parameterFactories);
|
||||
// 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");
|
||||
}
|
||||
|
||||
|
||||
@ -246,7 +247,7 @@ namespace Tapeti.Default
|
||||
try
|
||||
{
|
||||
method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -264,7 +265,7 @@ namespace Tapeti.Default
|
||||
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||
try
|
||||
{
|
||||
return (Task) method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||
return new ValueTask((Task) method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -275,32 +276,14 @@ namespace Tapeti.Default
|
||||
}
|
||||
|
||||
|
||||
private MessageHandlerFunc WrapGenericTaskMethod(MethodBase method, IEnumerable<ValueFactory> parameterFactories)
|
||||
{
|
||||
return context =>
|
||||
{
|
||||
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||
try
|
||||
{
|
||||
return (Task<object>)method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AddExceptionData(e);
|
||||
throw;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private MessageHandlerFunc WrapObjectMethod(MethodBase method, IEnumerable<ValueFactory> parameterFactories)
|
||||
private MessageHandlerFunc WrapValueTaskMethod(MethodBase method, IEnumerable<ValueFactory> parameterFactories)
|
||||
{
|
||||
return context =>
|
||||
{
|
||||
var controllerPayload = context.Get<ControllerMessageContextPayload>();
|
||||
try
|
||||
{
|
||||
return Task.FromResult(method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray()));
|
||||
return (ValueTask)method.Invoke(controllerPayload.Controller, parameterFactories.Select(p => p(context)).ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
@ -23,7 +23,7 @@ namespace Tapeti.Default
|
||||
return;
|
||||
|
||||
|
||||
var hasClassResult = context.Result.Info.ParameterType.IsTypeOrTaskOf(t => t.IsClass, out var isTaskOf, out var actualType);
|
||||
var hasClassResult = context.Result.Info.ParameterType.IsTypeOrTaskOf(t => t.IsClass, out var taskType, out var actualType);
|
||||
|
||||
var request = context.MessageClass?.GetCustomAttribute<RequestAttribute>();
|
||||
var expectedClassResult = request?.Response;
|
||||
@ -32,35 +32,55 @@ namespace Tapeti.Default
|
||||
// Tapeti 1.2: if you just want to publish another message as a result of the incoming message, explicitly call IPublisher.Publish.
|
||||
// ReSharper disable once ConvertIfStatementToSwitchStatement
|
||||
if (!hasClassResult && expectedClassResult != null || hasClassResult && expectedClassResult != actualType)
|
||||
throw new ArgumentException($"Message handler must return type {expectedClassResult?.FullName ?? "void"} in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}, found: {actualType?.FullName ?? "void"}");
|
||||
throw new ArgumentException($"Message handler for non-request message type {context.MessageClass?.FullName} must return type {expectedClassResult?.FullName ?? "void"} in controller {context.Method.DeclaringType?.FullName}, method {context.Method.Name}, found: {actualType?.FullName ?? "void"}");
|
||||
|
||||
if (!hasClassResult)
|
||||
return;
|
||||
|
||||
|
||||
|
||||
if (isTaskOf)
|
||||
switch (taskType)
|
||||
{
|
||||
var handler = GetType().GetMethod("PublishGenericTaskResult", BindingFlags.NonPublic | BindingFlags.Static)?.MakeGenericMethod(actualType);
|
||||
Debug.Assert(handler != null, nameof(handler) + " != null");
|
||||
case TaskType.None:
|
||||
context.Result.SetHandler((messageContext, value) => Reply(value, messageContext));
|
||||
break;
|
||||
|
||||
context.Result.SetHandler(async (messageContext, value) => { await (Task) handler.Invoke(null, new[] {messageContext, value }); });
|
||||
case TaskType.Task:
|
||||
var handler = GetType().GetMethod(nameof(PublishGenericTaskResult), BindingFlags.NonPublic | BindingFlags.Static)?.MakeGenericMethod(actualType);
|
||||
Debug.Assert(handler != null, nameof(handler) + " != null");
|
||||
|
||||
context.Result.SetHandler((messageContext, value) => (ValueTask)handler.Invoke(null, new[] { messageContext, value }));
|
||||
break;
|
||||
|
||||
case TaskType.ValueTask:
|
||||
var valueTaskHandler = GetType().GetMethod(nameof(PublishGenericValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)?.MakeGenericMethod(actualType);
|
||||
Debug.Assert(valueTaskHandler != null, nameof(handler) + " != null");
|
||||
|
||||
context.Result.SetHandler((messageContext, value) => (ValueTask)valueTaskHandler.Invoke(null, new[] { messageContext, value }));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
else
|
||||
context.Result.SetHandler((messageContext, value) => Reply(value, messageContext));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ReSharper disable once UnusedMember.Local - used implicitly above
|
||||
private static async Task PublishGenericTaskResult<T>(IMessageContext messageContext, object value) where T : class
|
||||
private static async ValueTask PublishGenericTaskResult<T>(IMessageContext messageContext, object value) where T : class
|
||||
{
|
||||
var message = await (Task<T>)value;
|
||||
await Reply(message, messageContext);
|
||||
}
|
||||
|
||||
|
||||
private static Task Reply(object message, IMessageContext messageContext)
|
||||
private static async ValueTask PublishGenericValueTaskResult<T>(IMessageContext messageContext, object value) where T : class
|
||||
{
|
||||
var message = await (ValueTask<T>)value;
|
||||
await Reply(message, messageContext);
|
||||
}
|
||||
|
||||
|
||||
private static async ValueTask Reply(object message, IMessageContext messageContext)
|
||||
{
|
||||
if (message == null)
|
||||
throw new ArgumentException("Return value of a request message handler must not be null");
|
||||
@ -71,9 +91,10 @@ namespace Tapeti.Default
|
||||
CorrelationId = messageContext.Properties.CorrelationId
|
||||
};
|
||||
|
||||
return !string.IsNullOrEmpty(messageContext.Properties.ReplyTo)
|
||||
? publisher.PublishDirect(message, messageContext.Properties.ReplyTo, properties, messageContext.Properties.Persistent.GetValueOrDefault(true))
|
||||
: publisher.Publish(message, properties, false);
|
||||
if (!string.IsNullOrEmpty(messageContext.Properties.ReplyTo))
|
||||
await publisher.PublishDirect(message, messageContext.Properties.ReplyTo, properties, messageContext.Properties.Persistent.GetValueOrDefault(true));
|
||||
else
|
||||
await publisher.Publish(message, properties, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ namespace Tapeti.Helpers
|
||||
/// <param name="handle">Receives the middleware which should be called and a reference to the action which will call the next. Pass this on to the middleware.</param>
|
||||
/// <param name="lastHandler">The action to execute when the innermost middleware calls next.</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static async Task GoAsync<T>(IReadOnlyList<T> middleware, Func<T, Func<Task>, Task> handle, Func<Task> lastHandler)
|
||||
public static async ValueTask GoAsync<T>(IReadOnlyList<T> middleware, Func<T, Func<ValueTask>, ValueTask> handle, Func<ValueTask> lastHandler)
|
||||
{
|
||||
var handlerIndex = middleware?.Count - 1 ?? -1;
|
||||
if (middleware == null || handlerIndex == -1)
|
||||
@ -54,7 +54,7 @@ namespace Tapeti.Helpers
|
||||
return;
|
||||
}
|
||||
|
||||
async Task HandleNext()
|
||||
async ValueTask HandleNext()
|
||||
{
|
||||
handlerIndex--;
|
||||
if (handlerIndex >= 0)
|
||||
|
@ -3,30 +3,59 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Tapeti.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if a type is a Task, ValueTask or other type.
|
||||
/// </summary>
|
||||
public enum TaskType
|
||||
{
|
||||
/// <summary>
|
||||
/// Type is not a Task or ValueTask.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Type is a Task or Task<T>
|
||||
/// </summary>
|
||||
Task,
|
||||
|
||||
/// <summary>
|
||||
/// Type is a ValueTask or ValueTask<T>
|
||||
/// </summary>
|
||||
ValueTask
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for working with synchronous and asynchronous versions of methods.
|
||||
/// </summary>
|
||||
public static class TaskTypeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if the given type matches the predicate, taking Task types into account.
|
||||
/// Determines if the given type matches the predicate, taking Task and ValueTask types into account.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="predicate"></param>
|
||||
/// <param name="isTaskOf"></param>
|
||||
/// <param name="taskType"></param>
|
||||
/// <param name="actualType"></param>
|
||||
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out bool isTaskOf, out Type actualType)
|
||||
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out TaskType taskType, out Type actualType)
|
||||
{
|
||||
if (type == typeof(Task))
|
||||
{
|
||||
isTaskOf = false;
|
||||
taskType = TaskType.Task;
|
||||
actualType = type;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == typeof(ValueTask))
|
||||
{
|
||||
taskType = TaskType.ValueTask;
|
||||
actualType = type;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
isTaskOf = true;
|
||||
taskType = TaskType.Task;
|
||||
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
if (genericArguments.Length == 1 && predicate(genericArguments[0]))
|
||||
@ -36,7 +65,19 @@ namespace Tapeti.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
isTaskOf = false;
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>))
|
||||
{
|
||||
taskType = TaskType.ValueTask;
|
||||
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
if (genericArguments.Length == 1 && predicate(genericArguments[0]))
|
||||
{
|
||||
actualType = genericArguments[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
taskType = TaskType.None;
|
||||
actualType = type;
|
||||
return predicate(type);
|
||||
}
|
||||
@ -47,10 +88,10 @@ namespace Tapeti.Helpers
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="predicate"></param>
|
||||
/// <param name="isTaskOf"></param>
|
||||
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out bool isTaskOf)
|
||||
/// <param name="taskType"></param>
|
||||
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out TaskType taskType)
|
||||
{
|
||||
return IsTypeOrTaskOf(type, predicate, out isTaskOf, out _);
|
||||
return IsTypeOrTaskOf(type, predicate, out taskType, out _);
|
||||
}
|
||||
|
||||
|
||||
@ -59,10 +100,10 @@ namespace Tapeti.Helpers
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="compareTo"></param>
|
||||
/// <param name="isTaskOf"></param>
|
||||
public static bool IsTypeOrTaskOf(this Type type, Type compareTo, out bool isTaskOf)
|
||||
/// <param name="taskType"></param>
|
||||
public static bool IsTypeOrTaskOf(this Type type, Type compareTo, out TaskType taskType)
|
||||
{
|
||||
return IsTypeOrTaskOf(type, t => t == compareTo, out isTaskOf);
|
||||
return IsTypeOrTaskOf(type, t => t == compareTo, out taskType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -43,7 +43,7 @@ namespace Tapeti
|
||||
var controllerIsObsolete = controller.GetCustomAttribute<ObsoleteAttribute>() != null;
|
||||
|
||||
|
||||
foreach (var method in controller.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
||||
foreach (var method in controller.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||
.Where(m => m.MemberType == MemberTypes.Method && m.DeclaringType != typeof(object) && (m as MethodInfo)?.IsSpecialName == false)
|
||||
.Select(m => (MethodInfo)m))
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user