Skip to content

Commit d46399a

Browse files
authored
Input / Output (danielgerlag#245)
1 parent bda699a commit d46399a

File tree

20 files changed

+367
-338
lines changed

20 files changed

+367
-338
lines changed

ReleaseNotes/next.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
## Action Inputs / Outputs
3+
4+
Added the action Input & Output overloads on the fluent step builder.
5+
6+
```c#
7+
Input(Action<TStepBody, TData> action);
8+
```
9+
10+
This will allow one to manipulate properties on the step before it executes and properties on the data object after it executes, for example
11+
12+
```c#
13+
Input((step, data) => step.Value1 = data.Value1)
14+
```
15+
16+
```c#
17+
.Output((step, data) => data["Value3"] = step.Output)
18+
```
19+
20+
```c#
21+
.Output((step, data) => data.MyCollection.Add(step.Output))
22+
```
23+
24+
## Breaking changes
25+
26+
The existing ability to assign values to entries in dictionaries or dynamic objects on `.Output` was problematic,
27+
since it broke the ability to pass collections on the Output mappings.
28+
29+
30+
```c#
31+
.Output(data => data["Value3"], step => step.Output)
32+
```
33+
34+
This feature has been removed, and it is advised to use the action Output API instead, for example
35+
36+
37+
```c#
38+
.Output((step, data) => data["Value3"] = step.Output)
39+
```
40+
41+
This functionality remains intact for JSON defined workflows.

WorkflowCore.sln

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReleaseNotes", "ReleaseNote
9595
ReleaseNotes\1.6.8.md = ReleaseNotes\1.6.8.md
9696
ReleaseNotes\1.6.9.md = ReleaseNotes\1.6.9.md
9797
ReleaseNotes\1.7.0.md = ReleaseNotes\1.7.0.md
98+
ReleaseNotes\next.md = ReleaseNotes\next.md
9899
EndProjectSection
99100
EndProject
100101
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample14", "src\samples\WorkflowCore.Sample14\WorkflowCore.Sample14.csproj", "{6BC66637-B42A-4334-ADFB-DBEC9F29D293}"
@@ -123,7 +124,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.MySQL",
123124
EndProject
124125
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.DynamoDB", "test\WorkflowCore.Tests.DynamoDB\WorkflowCore.Tests.DynamoDB.csproj", "{3ECEC028-7E2C-4983-B928-26C073B51BB7}"
125126
EndProject
126-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Providers.Redis", "src\providers\WorkflowCore.Providers.Redis\WorkflowCore.Providers.Redis.csproj", "{435C6263-C6F8-4E93-B417-D861E9C22E18}"
127+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Providers.Redis", "src\providers\WorkflowCore.Providers.Redis\WorkflowCore.Providers.Redis.csproj", "{435C6263-C6F8-4E93-B417-D861E9C22E18}"
127128
EndProject
128129
Global
129130
GlobalSection(SolutionConfigurationPlatforms) = preSolution

src/WorkflowCore/Interface/IStepBuilder.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ public interface IStepBuilder<TData, TStepBody>
7777
/// <returns></returns>
7878
IStepBuilder<TData, TStepBody> Input<TInput>(Expression<Func<TStepBody, TInput>> stepProperty, Expression<Func<TData, IStepExecutionContext, TInput>> value);
7979

80+
/// <summary>
81+
/// Manipulate properties on the step before its executed.
82+
/// </summary>
83+
/// <param name="action"></param>
84+
/// <returns></returns>
85+
IStepBuilder<TData, TStepBody> Input(Action<TStepBody, TData> action);
86+
8087
/// <summary>
8188
/// Map properties on the workflow data object to properties on the step after the step executes
8289
/// </summary>
@@ -86,6 +93,13 @@ public interface IStepBuilder<TData, TStepBody>
8693
/// <returns></returns>
8794
IStepBuilder<TData, TStepBody> Output<TOutput>(Expression<Func<TData, TOutput>> dataProperty, Expression<Func<TStepBody, object>> value);
8895

96+
/// <summary>
97+
/// Manipulate properties on the data object after the step executes
98+
/// </summary>
99+
/// <param name="action"></param>
100+
/// <returns></returns>
101+
IStepBuilder<TData, TStepBody> Output(Action<TStepBody, TData> action);
102+
89103
/// <summary>
90104
/// Wait here until to specified event is published
91105
/// </summary>
@@ -222,4 +236,4 @@ public interface IStepBuilder<TData, TStepBody>
222236
IStepBuilder<TData, TStepBody> CancelCondition(Expression<Func<TData, bool>> cancelCondition, bool proceedAfterCancel = false);
223237

224238
}
225-
}
239+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using WorkflowCore.Interface;
2+
using WorkflowCore.Models;
3+
4+
namespace WorkflowCore.Interface
5+
{
6+
public interface IStepParameter
7+
{
8+
void AssignInput(object data, IStepBody body, IStepExecutionContext context);
9+
void AssignOutput(object data, IStepBody body, IStepExecutionContext context);
10+
}
11+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using WorkflowCore.Interface;
6+
7+
namespace WorkflowCore.Models
8+
{
9+
public class ActionParameter<TStepBody, TData> : IStepParameter
10+
{
11+
private readonly Action<TStepBody, TData> _action;
12+
13+
public ActionParameter(Action<TStepBody, TData> action)
14+
{
15+
_action = action;
16+
}
17+
18+
private void Assign(object data, IStepBody step, IStepExecutionContext context)
19+
{
20+
_action.Invoke((TStepBody)step, (TData)data);
21+
}
22+
23+
public void AssignInput(object data, IStepBody body, IStepExecutionContext context)
24+
{
25+
Assign(data, body, context);
26+
}
27+
28+
public void AssignOutput(object data, IStepBody body, IStepExecutionContext context)
29+
{
30+
Assign(data, body, context);
31+
}
32+
}
33+
}

src/WorkflowCore/Models/DataMapping.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using WorkflowCore.Interface;
6+
7+
namespace WorkflowCore.Models
8+
{
9+
public class MemberMapParameter : IStepParameter
10+
{
11+
private readonly LambdaExpression _source;
12+
private readonly LambdaExpression _target;
13+
14+
public MemberMapParameter(LambdaExpression source, LambdaExpression target)
15+
{
16+
if (target.Body.NodeType != ExpressionType.MemberAccess)
17+
throw new NotSupportedException();
18+
19+
_source = source;
20+
_target = target;
21+
}
22+
23+
private void Assign(object sourceObject, LambdaExpression sourceExpr, object targetObject, LambdaExpression targetExpr, IStepExecutionContext context)
24+
{
25+
object resolvedValue = null;
26+
27+
switch (sourceExpr.Parameters.Count)
28+
{
29+
case 1:
30+
resolvedValue = sourceExpr.Compile().DynamicInvoke(sourceObject);
31+
break;
32+
case 2:
33+
resolvedValue = sourceExpr.Compile().DynamicInvoke(sourceObject, context);
34+
break;
35+
default:
36+
throw new ArgumentException();
37+
}
38+
39+
if (resolvedValue == null)
40+
{
41+
var defaultAssign = Expression.Lambda(Expression.Assign(targetExpr.Body, Expression.Default(targetExpr.ReturnType)), targetExpr.Parameters.Single());
42+
defaultAssign.Compile().DynamicInvoke(targetObject);
43+
return;
44+
}
45+
46+
var valueExpr = Expression.Convert(Expression.Constant(resolvedValue), targetExpr.ReturnType);
47+
var assign = Expression.Lambda(Expression.Assign(targetExpr.Body, valueExpr), targetExpr.Parameters.Single());
48+
assign.Compile().DynamicInvoke(targetObject);
49+
}
50+
51+
public void AssignInput(object data, IStepBody body, IStepExecutionContext context)
52+
{
53+
Assign(data, _source, body, _target, context);
54+
}
55+
56+
public void AssignOutput(object data, IStepBody body, IStepExecutionContext context)
57+
{
58+
Assign(body, _source, data, _target, context);
59+
}
60+
}
61+
}

src/WorkflowCore/Models/WorkflowStep.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public abstract class WorkflowStep
2020

2121
public virtual List<StepOutcome> Outcomes { get; set; } = new List<StepOutcome>();
2222

23-
public virtual List<DataMapping> Inputs { get; set; } = new List<DataMapping>();
23+
public virtual List<IStepParameter> Inputs { get; set; } = new List<IStepParameter>();
2424

25-
public virtual List<DataMapping> Outputs { get; set; } = new List<DataMapping>();
25+
public virtual List<IStepParameter> Outputs { get; set; } = new List<IStepParameter>();
2626

2727
public virtual WorkflowErrorHandling? ErrorBehavior { get; set; }
2828

src/WorkflowCore/Services/DefinitionStorage/DefinitionLoader.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,7 @@ private void AttachInputs(StepSourceV1 source, Type dataType, Type stepType, Wor
177177
var targetProperty = Expression.Property(stepParameter, input.Key);
178178
var targetExpr = Expression.Lambda(targetProperty, stepParameter);
179179

180-
step.Inputs.Add(new DataMapping
181-
{
182-
Source = sourceExpr,
183-
Target = targetExpr
184-
});
180+
step.Inputs.Add(new MemberMapParameter(sourceExpr, targetExpr));
185181
}
186182
}
187183

@@ -200,20 +196,23 @@ private void AttachOutputs(StepSourceV1 source, Type dataType, Type stepType, Wo
200196
if (propertyInfo != null)
201197
{
202198
targetProperty = Expression.Property(dataParameter, propertyInfo);
199+
var targetExpr = Expression.Lambda(targetProperty, dataParameter);
200+
step.Outputs.Add(new MemberMapParameter(sourceExpr, targetExpr));
203201
}
204202
else
205203
{
206204
// If we did not find a matching property try to find a Indexer with string parameter
207205
propertyInfo = dataType.GetProperty("Item");
208206
targetProperty = Expression.Property(dataParameter, propertyInfo, Expression.Constant(output.Key));
209-
}
210-
var targetExpr = Expression.Lambda(targetProperty, dataParameter);
207+
208+
Action<IStepBody, object> acn = (pStep, pData) =>
209+
{
210+
object resolvedValue = sourceExpr.Compile().DynamicInvoke(pStep); ;
211+
propertyInfo.SetValue(pData, resolvedValue, new object[] { output.Key });
212+
};
211213

212-
step.Outputs.Add(new DataMapping
213-
{
214-
Source = sourceExpr,
215-
Target = targetExpr
216-
});
214+
step.Outputs.Add(new ActionParameter<IStepBody, object>(acn));
215+
}
217216
}
218217
}
219218

src/WorkflowCore/Services/FluentBuilders/ExpressionHelpers.cs

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)