diff --git a/Publish.ps1 b/Publish.ps1
deleted file mode 100644
index 7646e36..0000000
--- a/Publish.ps1
+++ /dev/null
@@ -1,55 +0,0 @@
-param([switch]$nopush)
-
-
-function pack
-{
- param([string]$project)
-
- Write-Host "Packing $($project).csproj" -Foreground Blue
- NuGet.exe pack "$($project)\$($project).csproj" -Build -OutputDir publish -Version "$($version.NuGetVersion)" -Properties depversion="$($version.NuGetVersion)"
-}
-
-
-function push
-{
- param([string]$project)
-
- Write-Host "Pushing $($project).csproj" -Foreground Blue
- NuGet.exe push "publish\X2Software.$($project).$($version.NuGetVersion).nupkg" -apikey "$($nugetkey)" -Source https://www.nuget.org/api/v2/package
-}
-
-
-$projects = @(
- "Tapeti.Annotations",
- "Tapeti",
- "Tapeti.DataAnnotations",
- "Tapeti.Flow",
- "Tapeti.SimpleInjector"
-)
-
-
-New-Item -Path publish -Type directory -Force | Out-Null
-
-$version = GitVersion.exe | Out-String | ConvertFrom-Json
-$nugetkey = Get-Content .nuget.apikey
-
-
-Write-Host "Publishing version $($version.NuGetVersion) using API key $($nugetkey)"-Foreground Cyan
-
-foreach ($project in $projects)
-{
- pack($project)
-}
-
-
-if ($nopush -eq $false)
-{
- foreach ($project in $projects)
- {
- push($project)
- }
-}
-else
-{
- Write-Host "Skipping push" -Foreground Blue
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index 4d31212..c35e158 100644
--- a/README.md
+++ b/README.md
@@ -6,3 +6,14 @@ The documentation for Tapeti is available on Read the Docs:
[Master branch](http://tapeti.readthedocs.io/en/stable/)
[![Documentation Status](https://readthedocs.org/projects/tapeti/badge/?version=stable)](http://tapeti.readthedocs.io/en/stable/?badge=stable)
+
+
+## Builds
+Builds are automatically run using AppVeyor, with the resulting packages being pushed to NuGet.
+
+
+Latest build
+[![Build status](https://ci.appveyor.com/api/projects/status/cyuo0vm7admy0d9x?svg=true)](https://ci.appveyor.com/project/MvRens/tapeti)
+
+Master build
+[![Build status](https://ci.appveyor.com/api/projects/status/cyuo0vm7admy0d9x/branch/master?svg=true)](https://ci.appveyor.com/project/MvRens/tapeti/branch/master)
diff --git a/Tapeti.Annotations/Tapeti.Annotations.nuspec b/Tapeti.Annotations/Tapeti.Annotations.nuspec
index acceaf2..d2684ac 100644
--- a/Tapeti.Annotations/Tapeti.Annotations.nuspec
+++ b/Tapeti.Annotations/Tapeti.Annotations.nuspec
@@ -1,17 +1,17 @@
-
-
-
- X2Software.Tapeti.Annotations
- $version$
- $title$
- Mark van Renswoude
- Mark van Renswoude
- https://git.x2software.net/pub/tapeti/raw/master/UNLICENSE
- https://git.x2software.net/pub/tapeti
- https://git.x2software.net/pub/tapeti/raw/master/resources/icons/Tapeti.Annotations.png
- false
- Annotations for Tapeti
-
- rabbitmq tapeti
-
+
+
+
+ X2Software.Tapeti.Annotations
+ $version$
+ Tapeti Annotations
+ Mark van Renswoude
+ Mark van Renswoude
+ https://raw.githubusercontent.com/MvRens/Tapeti/master/UNLICENSE
+ https://github.com/MvRens/Tapeti
+ https://raw.githubusercontent.com/MvRens/Tapeti/master/resources/icons/Tapeti.Annotations.png
+ false
+ Annotations for Tapeti
+
+ rabbitmq tapeti
+
\ No newline at end of file
diff --git a/Tapeti.DataAnnotations/Tapeti.DataAnnotations.nuspec b/Tapeti.DataAnnotations/Tapeti.DataAnnotations.nuspec
index a1b9042..571c3a5 100644
--- a/Tapeti.DataAnnotations/Tapeti.DataAnnotations.nuspec
+++ b/Tapeti.DataAnnotations/Tapeti.DataAnnotations.nuspec
@@ -1,20 +1,20 @@
-
-
-
- X2Software.Tapeti.DataAnnotations
- $version$
- $title$
- Mark van Renswoude
- Mark van Renswoude
- https://git.x2software.net/pub/tapeti/raw/master/UNLICENSE
- https://git.x2software.net/pub/tapeti
- https://git.x2software.net/pub/tapeti/raw/master/resources/icons/Tapeti.DataAnnotations.png
- false
- DataAnnotations validation extension for Tapeti
-
- rabbitmq tapeti dataannotations
-
-
-
-
+
+
+
+ X2Software.Tapeti.DataAnnotations
+ $version$
+ Tapeti DataAnnotations
+ Mark van Renswoude
+ Mark van Renswoude
+ https://raw.githubusercontent.com/MvRens/Tapeti/master/UNLICENSE
+ https://github.com/MvRens/Tapeti
+ https://raw.githubusercontent.com/MvRens/Tapeti/master/resources/icons/Tapeti.DataAnnotations.png
+ false
+ DataAnnotations validation extension for Tapeti
+
+ rabbitmq tapeti dataannotations
+
+
+
+
\ No newline at end of file
diff --git a/Tapeti.Flow.SQL/ConfigExtensions.cs b/Tapeti.Flow.SQL/ConfigExtensions.cs
index 70b1aff..c5e660d 100644
--- a/Tapeti.Flow.SQL/ConfigExtensions.cs
+++ b/Tapeti.Flow.SQL/ConfigExtensions.cs
@@ -30,7 +30,7 @@ namespace Tapeti.Flow.SQL
public void RegisterDefaults(IDependencyContainer container)
{
- container.RegisterDefault>(() => new SqlConnectionFlowRepository(connectionString, serviceId, schema));
+ container.RegisterDefault(() => new SqlConnectionFlowRepository(connectionString, serviceId, schema));
}
diff --git a/Tapeti.Flow.SQL/SqlConnectionFlowRepository.cs b/Tapeti.Flow.SQL/SqlConnectionFlowRepository.cs
index 4af9d05..78e47a7 100644
--- a/Tapeti.Flow.SQL/SqlConnectionFlowRepository.cs
+++ b/Tapeti.Flow.SQL/SqlConnectionFlowRepository.cs
@@ -24,7 +24,7 @@ namespace Tapeti.Flow.SQL
);
go;
*/
- public class SqlConnectionFlowRepository : IFlowRepository
+ public class SqlConnectionFlowRepository : IFlowRepository
{
private readonly string connectionString;
private readonly int serviceId;
@@ -39,7 +39,7 @@ namespace Tapeti.Flow.SQL
}
- public async Task>> GetStates()
+ public async Task>> GetStates()
{
using (var connection = await GetConnection())
{
@@ -69,14 +69,14 @@ namespace Tapeti.Flow.SQL
}
- public Task CreateState(Guid flowID, T state, DateTime timestamp)
+ public Task CreateState(Guid flowID, T state, DateTime timestamp)
{
var stateJason = JsonConvert.SerializeObject(state);
throw new NotImplementedException();
}
- public Task UpdateState(Guid flowID, T state)
+ public Task UpdateState(Guid flowID, T state)
{
throw new NotImplementedException();
}
diff --git a/Tapeti.Flow.SQL/Tapeti.Flow.SQL.nuspec b/Tapeti.Flow.SQL/Tapeti.Flow.SQL.nuspec
index 686bbf9..0a667c6 100644
--- a/Tapeti.Flow.SQL/Tapeti.Flow.SQL.nuspec
+++ b/Tapeti.Flow.SQL/Tapeti.Flow.SQL.nuspec
@@ -1,21 +1,21 @@
-
-
-
- X2Software.Tapeti.Flow.SQL
- $version$
- $title$
- Mark van Renswoude
- Mark van Renswoude
- https://git.x2software.net/pub/tapeti/raw/master/UNLICENSE
- https://git.x2software.net/pub/tapeti
- https://git.x2software.net/pub/tapeti/raw/master/resources/icons/Tapeti.Flow.SQL.png
- false
- SQL backing repository for the Tapeti Flow package
-
- rabbitmq tapeti sql
-
-
-
-
-
+
+
+
+ X2Software.Tapeti.Flow.SQL
+ $version$
+ Tapeti Flow SQL
+ Mark van Renswoude
+ Mark van Renswoude
+ https://raw.githubusercontent.com/MvRens/Tapeti/master/UNLICENSE
+ https://github.com/MvRens/Tapeti
+ https://raw.githubusercontent.com/MvRens/Tapeti/master/resources/icons/Tapeti.Flow.SQL.png
+ false
+ SQL backing repository for the Tapeti Flow package
+
+ rabbitmq tapeti sql
+
+
+
+
+
\ No newline at end of file
diff --git a/Tapeti.Flow/ConfigExtensions.cs b/Tapeti.Flow/ConfigExtensions.cs
index 843333e..127a0c2 100644
--- a/Tapeti.Flow/ConfigExtensions.cs
+++ b/Tapeti.Flow/ConfigExtensions.cs
@@ -2,7 +2,7 @@
{
public static class ConfigExtensions
{
- public static TapetiConfig WithFlow(this TapetiConfig config, IFlowRepository flowRepository = null)
+ public static TapetiConfig WithFlow(this TapetiConfig config, IFlowRepository flowRepository = null)
{
config.Use(new FlowMiddleware(flowRepository));
return config;
diff --git a/Tapeti.Flow/Default/DelegateYieldPoint.cs b/Tapeti.Flow/Default/DelegateYieldPoint.cs
index 984f4cd..2ed0926 100644
--- a/Tapeti.Flow/Default/DelegateYieldPoint.cs
+++ b/Tapeti.Flow/Default/DelegateYieldPoint.cs
@@ -3,16 +3,13 @@ using System.Threading.Tasks;
namespace Tapeti.Flow.Default
{
- internal class DelegateYieldPoint : IExecutableYieldPoint
+ internal class DelegateYieldPoint : IYieldPoint
{
- public bool StoreState { get; }
-
private readonly Func onExecute;
- public DelegateYieldPoint(bool storeState, Func onExecute)
+ public DelegateYieldPoint(Func onExecute)
{
- StoreState = storeState;
this.onExecute = onExecute;
}
diff --git a/Tapeti.Flow/Default/FlowBindingMiddleware.cs b/Tapeti.Flow/Default/FlowBindingMiddleware.cs
index d7e279a..9053135 100644
--- a/Tapeti.Flow/Default/FlowBindingMiddleware.cs
+++ b/Tapeti.Flow/Default/FlowBindingMiddleware.cs
@@ -87,7 +87,7 @@ namespace Tapeti.Flow.Default
private static Task HandleParallelResponse(IMessageContext context)
{
var flowHandler = context.DependencyResolver.Resolve();
- return flowHandler.Execute(context, new StateYieldPoint(true));
+ return flowHandler.Execute(context, new DelegateYieldPoint((a) => Task.CompletedTask));
}
diff --git a/Tapeti.Flow/Default/FlowCleanupMiddleware.cs b/Tapeti.Flow/Default/FlowCleanupMiddleware.cs
new file mode 100644
index 0000000..16cf61b
--- /dev/null
+++ b/Tapeti.Flow/Default/FlowCleanupMiddleware.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tapeti.Config;
+
+namespace Tapeti.Flow.Default
+{
+ public class FlowCleanupMiddleware : ICleanupMiddleware
+ {
+ public async Task Handle(IMessageContext context, HandlingResult handlingResult)
+ {
+ object flowContextObj;
+ if (!context.Items.TryGetValue(ContextItems.FlowContext, out flowContextObj))
+ return;
+ var flowContext = (FlowContext)flowContextObj;
+
+ if (flowContext?.FlowStateLock != null)
+ {
+ if (handlingResult.ConsumeResponse == ConsumeResponse.Nack
+ || handlingResult.MessageAction == MessageAction.ErrorLog)
+ {
+ await flowContext.FlowStateLock.DeleteFlowState();
+ }
+ flowContext.FlowStateLock.Dispose();
+ }
+ }
+ }
+}
diff --git a/Tapeti.Flow/Default/FlowContext.cs b/Tapeti.Flow/Default/FlowContext.cs
index 460d5b2..dbadf08 100644
--- a/Tapeti.Flow/Default/FlowContext.cs
+++ b/Tapeti.Flow/Default/FlowContext.cs
@@ -13,13 +13,13 @@ namespace Tapeti.Flow.Default
public Guid ContinuationID { get; set; }
public ContinuationMetadata ContinuationMetadata { get; set; }
- private bool stored;
+ private bool storeCalled;
+ private bool deleteCalled;
- public async Task EnsureStored()
+ public async Task Store()
{
- if (stored)
- return;
+ storeCalled = true;
if (MessageContext == null) throw new ArgumentNullException(nameof(MessageContext));
if (FlowState == null) throw new ArgumentNullException(nameof(FlowState));
@@ -27,8 +27,20 @@ namespace Tapeti.Flow.Default
FlowState.Data = Newtonsoft.Json.JsonConvert.SerializeObject(MessageContext.Controller);
await FlowStateLock.StoreFlowState(FlowState);
+ }
- stored = true;
+ public async Task Delete()
+ {
+ deleteCalled = true;
+
+ if (FlowStateLock != null)
+ await FlowStateLock.DeleteFlowState();
+ }
+
+ public void EnsureStoreOrDeleteIsCalled()
+ {
+ if (!storeCalled && !deleteCalled)
+ throw new InvalidProgramException("Neither Store nor Delete are called for the state of the current flow. FlowID = " + FlowStateLock?.FlowID);
}
public void Dispose()
diff --git a/Tapeti.Flow/Default/FlowMessageFilterMiddleware.cs b/Tapeti.Flow/Default/FlowMessageFilterMiddleware.cs
index 1d327ae..8c66e9d 100644
--- a/Tapeti.Flow/Default/FlowMessageFilterMiddleware.cs
+++ b/Tapeti.Flow/Default/FlowMessageFilterMiddleware.cs
@@ -39,8 +39,6 @@ namespace Tapeti.Flow.Default
return null;
var flowStateLock = await flowStore.LockFlowState(flowID.Value);
- if (flowStateLock == null)
- return null;
var flowState = await flowStateLock.GetFlowState();
if (flowState == null)
diff --git a/Tapeti.Flow/Default/FlowProvider.cs b/Tapeti.Flow/Default/FlowProvider.cs
index 88bc9f6..308cb3a 100644
--- a/Tapeti.Flow/Default/FlowProvider.cs
+++ b/Tapeti.Flow/Default/FlowProvider.cs
@@ -26,13 +26,13 @@ namespace Tapeti.Flow.Default
public IYieldPoint YieldWithRequest(TRequest message, Func> responseHandler)
{
var responseHandlerInfo = GetResponseHandlerInfo(config, message, responseHandler);
- return new DelegateYieldPoint(true, context => SendRequest(context, message, responseHandlerInfo));
+ return new DelegateYieldPoint(context => SendRequest(context, message, responseHandlerInfo));
}
public IYieldPoint YieldWithRequestSync(TRequest message, Func responseHandler)
{
var responseHandlerInfo = GetResponseHandlerInfo(config, message, responseHandler);
- return new DelegateYieldPoint(true, context => SendRequest(context, message, responseHandlerInfo));
+ return new DelegateYieldPoint(context => SendRequest(context, message, responseHandlerInfo));
}
public IFlowParallelRequestBuilder YieldWithParallelRequest()
@@ -42,18 +42,23 @@ namespace Tapeti.Flow.Default
public IYieldPoint EndWithResponse(TResponse message)
{
- return new DelegateYieldPoint(false, context => SendResponse(context, message));
+ return new DelegateYieldPoint(context => SendResponse(context, message));
}
public IYieldPoint End()
{
- return new DelegateYieldPoint(false, EndFlow);
+ return new DelegateYieldPoint(EndFlow);
}
private async Task SendRequest(FlowContext context, object message, ResponseHandlerInfo responseHandlerInfo,
string convergeMethodName = null, bool convergeMethodTaskSync = false)
{
+ if (context.FlowState == null)
+ {
+ await CreateNewFlowState(context);
+ }
+
var continuationID = Guid.NewGuid();
context.FlowState.Continuations.Add(continuationID,
@@ -70,14 +75,18 @@ namespace Tapeti.Flow.Default
ReplyTo = responseHandlerInfo.ReplyToQueue
};
- await context.EnsureStored();
+ await context.Store();
+
await publisher.Publish(message, properties);
}
private async Task SendResponse(FlowContext context, object message)
{
- var reply = context.FlowState.Metadata.Reply;
+ var reply = context.FlowState == null
+ ? GetReply(context.MessageContext)
+ : context.FlowState.Metadata.Reply;
+
if (reply == null)
throw new YieldPointException("No response is required");
@@ -92,19 +101,21 @@ namespace Tapeti.Flow.Default
properties.CorrelationId = reply.CorrelationId;
// TODO disallow if replyto is not specified?
- if (context.FlowState.Metadata.Reply.ReplyTo != null)
+ if (reply.ReplyTo != null)
await publisher.PublishDirect(message, reply.ReplyTo, properties);
else
await publisher.Publish(message, properties);
+
+ await context.Delete();
}
- private static Task EndFlow(FlowContext context)
+ private static async Task EndFlow(FlowContext context)
{
- if (context.FlowState.Metadata.Reply != null)
- throw new YieldPointException($"Flow must end with a response message of type {context.FlowState.Metadata.Reply.ResponseTypeName}");
+ await context.Delete();
- return Task.CompletedTask;
+ if (context.FlowState != null && context.FlowState.Metadata.Reply != null)
+ throw new YieldPointException($"Flow must end with a response message of type {context.FlowState.Metadata.Reply.ResponseTypeName}");
}
@@ -147,11 +158,31 @@ namespace Tapeti.Flow.Default
};
}
+ private async Task CreateNewFlowState(FlowContext flowContext)
+ {
+ var flowStore = flowContext.MessageContext.DependencyResolver.Resolve();
+
+ var flowID = Guid.NewGuid();
+ flowContext.FlowStateLock = await flowStore.LockFlowState(flowID);
+
+ if (flowContext.FlowStateLock == null)
+ throw new InvalidOperationException("Unable to lock a new flow");
+
+ flowContext.FlowState = new FlowState
+ {
+ Metadata = new FlowMetadata
+ {
+ Reply = GetReply(flowContext.MessageContext)
+ }
+ };
+ }
public async Task Execute(IMessageContext context, IYieldPoint yieldPoint)
{
- var executableYieldPoint = yieldPoint as IExecutableYieldPoint;
- var storeState = executableYieldPoint?.StoreState ?? false;
+ var executableYieldPoint = yieldPoint as DelegateYieldPoint;
+
+ if (executableYieldPoint == null)
+ throw new YieldPointException($"Yield point is required in controller {context.Controller.GetType().Name} for method {context.Binding.Method.Name}");
FlowContext flowContext;
object flowContextItem;
@@ -160,27 +191,10 @@ namespace Tapeti.Flow.Default
{
flowContext = new FlowContext
{
- MessageContext = context,
- FlowState = new FlowState()
+ MessageContext = context
};
- if (storeState)
- {
- // Initiate the flow
- var flowStore = context.DependencyResolver.Resolve();
-
- var flowID = Guid.NewGuid();
- flowContext.FlowStateLock = await flowStore.LockFlowState(flowID);
-
- if (flowContext.FlowStateLock == null)
- throw new InvalidOperationException("Unable to lock a new flow");
-
- flowContext.FlowState = await flowContext.FlowStateLock.GetFlowState();
- if (flowContext.FlowState == null)
- throw new InvalidOperationException("Unable to get state for new flow");
-
- flowContext.FlowState.Metadata.Reply = GetReply(context);
- }
+ context.Items.Add(ContextItems.FlowContext, flowContext);
}
else
flowContext = (FlowContext)flowContextItem;
@@ -193,19 +207,17 @@ namespace Tapeti.Flow.Default
}
catch (YieldPointException e)
{
- var controllerName = flowContext.MessageContext.Controller.GetType().FullName;
- var methodName = flowContext.MessageContext.Binding.Method.Name;
-
- throw new YieldPointException($"{e.Message} in controller {controllerName}, method {methodName}", e);
+ // Useful for debugging
+ e.Data["Tapeti.Controller.Name"] = context.Controller.GetType().FullName;
+ e.Data["Tapeti.Controller.Method"] = context.Binding.Method.Name;
+ throw;
}
- if (storeState)
- await flowContext.EnsureStored();
- else if (flowContext.FlowStateLock != null)
- await flowContext.FlowStateLock.DeleteFlowState();
+ flowContext.EnsureStoreOrDeleteIsCalled();
}
+
private class ParallelRequestBuilder : IFlowParallelRequestBuilder
{
public delegate Task SendRequestFunc(FlowContext context,
@@ -275,7 +287,7 @@ namespace Tapeti.Flow.Default
if (convergeMethod?.Method == null)
throw new ArgumentNullException(nameof(convergeMethod));
- return new DelegateYieldPoint(true, context =>
+ return new DelegateYieldPoint(context =>
{
if (convergeMethod.Method.DeclaringType != context.MessageContext.Controller.GetType())
throw new YieldPointException("Converge method must be in the same controller class");
diff --git a/Tapeti.Flow/Default/FlowStarter.cs b/Tapeti.Flow/Default/FlowStarter.cs
index a21593f..306f034 100644
--- a/Tapeti.Flow/Default/FlowStarter.cs
+++ b/Tapeti.Flow/Default/FlowStarter.cs
@@ -10,30 +10,42 @@ namespace Tapeti.Flow.Default
public class FlowStarter : IFlowStarter
{
private readonly IConfig config;
+ private readonly ILogger logger;
- public FlowStarter(IConfig config)
+ public FlowStarter(IConfig config, ILogger logger)
{
this.config = config;
+ this.logger = logger;
}
public Task Start(Expression>> methodSelector) where TController : class
{
- return CallControllerMethod(GetExpressionMethod(methodSelector), value => Task.FromResult((IYieldPoint)value));
+ return CallControllerMethod(GetExpressionMethod(methodSelector), value => Task.FromResult((IYieldPoint)value), new object[] { });
}
public Task Start(Expression>>> methodSelector) where TController : class
{
- return CallControllerMethod(GetExpressionMethod(methodSelector), value => (Task)value);
+ return CallControllerMethod(GetExpressionMethod(methodSelector), value => (Task)value, new object[] {});
+ }
+
+ public Task Start(Expression>> methodSelector, TParameter parameter) where TController : class
+ {
+ return CallControllerMethod(GetExpressionMethod(methodSelector), value => Task.FromResult((IYieldPoint)value), new object[] {parameter});
+ }
+
+ public Task Start(Expression>>> methodSelector, TParameter parameter) where TController : class
+ {
+ return CallControllerMethod(GetExpressionMethod(methodSelector), value => (Task)value, new object[] {parameter});
}
- private async Task CallControllerMethod(MethodInfo method, Func