Skip to content

Commit 29600de

Browse files
authored
Merge pull request danielgerlag#173 from dr-BEat/feat/DictionaryDataSupport
Added support for indexers instead of properties for the DataType.
2 parents 5409367 + de172a0 commit 29600de

File tree

15 files changed

+428
-15
lines changed

15 files changed

+428
-15
lines changed

src/WorkflowCore/Services/DefinitionStorage/DefinitionLoader.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,15 @@ private void AttachInputs(StepSourceV1 source, Type dataType, Type stepType, Wor
173173
var dataParameter = Expression.Parameter(dataType, "data");
174174
var contextParameter = Expression.Parameter(typeof(IStepExecutionContext), "context");
175175
var sourceExpr = DynamicExpressionParser.ParseLambda(new [] { dataParameter, contextParameter }, typeof(object), input.Value);
176-
var targetExpr = Expression.Property(Expression.Parameter(stepType), input.Key);
177176

178-
step.Inputs.Add(new DataMapping()
177+
var stepParameter = Expression.Parameter(stepType, "step");
178+
var targetProperty = Expression.Property(stepParameter, input.Key);
179+
var targetExpr = Expression.Lambda(targetProperty, stepParameter);
180+
181+
step.Inputs.Add(new DataMapping
179182
{
180183
Source = sourceExpr,
181-
Target = Expression.Lambda(targetExpr)
184+
Target = targetExpr
182185
});
183186
}
184187
}
@@ -189,12 +192,28 @@ private void AttachOutputs(StepSourceV1 source, Type dataType, Type stepType, Wo
189192
{
190193
var stepParameter = Expression.Parameter(stepType, "step");
191194
var sourceExpr = DynamicExpressionParser.ParseLambda(new[] { stepParameter }, typeof(object), output.Value);
192-
var targetExpr = Expression.Property(Expression.Parameter(dataType), output.Key);
193195

194-
step.Outputs.Add(new DataMapping()
196+
var dataParameter = Expression.Parameter(dataType, "data");
197+
Expression targetProperty;
198+
199+
// Check if our datatype has a matching property
200+
var propertyInfo = dataType.GetProperty(output.Key);
201+
if (propertyInfo != null)
202+
{
203+
targetProperty = Expression.Property(dataParameter, propertyInfo);
204+
}
205+
else
206+
{
207+
// If we did not find a matching property try to find a Indexer with string parameter
208+
propertyInfo = dataType.GetProperty("Item");
209+
targetProperty = Expression.Property(dataParameter, propertyInfo, Expression.Constant(output.Key));
210+
}
211+
var targetExpr = Expression.Lambda(targetProperty, dataParameter);
212+
213+
step.Outputs.Add(new DataMapping
195214
{
196215
Source = sourceExpr,
197-
Target = Expression.Lambda(targetExpr)
216+
Target = targetExpr
198217
});
199218
}
200219
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
6+
namespace WorkflowCore.Services.FluentBuilders
7+
{
8+
internal static class ExpressionHelpers
9+
{
10+
public static Expression<Action<TObject, TParam>> CreateSetter<TObject, TParam>(
11+
Expression<Func<TObject, TParam>> getterExpression)
12+
{
13+
var valueParameter = Expression.Parameter(typeof(TParam), "value");
14+
Expression assignment;
15+
if (getterExpression.Body is MethodCallExpression callExpression && callExpression.Method.Name == "get_Item")
16+
{
17+
//Get Matching setter method for the indexer
18+
var parameterTypes = callExpression.Method.GetParameters()
19+
.Select(p => p.ParameterType)
20+
.ToArray();
21+
var itemProperty = callExpression.Method.DeclaringType.GetProperty("Item", typeof(TParam), parameterTypes);
22+
23+
assignment = Expression.Call(callExpression.Object, itemProperty.SetMethod, callExpression.Arguments.Concat(new[] { valueParameter }));
24+
}
25+
else
26+
{
27+
assignment = Expression.Assign(getterExpression.Body, valueParameter);
28+
}
29+
return Expression.Lambda<Action<TObject, TParam>>(assignment, valueParameter);
30+
}
31+
32+
public static LambdaExpression CreateSetter(LambdaExpression getterExpression)
33+
{
34+
var valueParameter = Expression.Parameter(getterExpression.ReturnType, "value");
35+
Expression assignment;
36+
if (getterExpression.Body is MethodCallExpression callExpression && callExpression.Method.Name == "get_Item")
37+
{
38+
//Get Matching setter method for the indexer
39+
var parameterTypes = callExpression.Method.GetParameters()
40+
.Select(p => p.ParameterType)
41+
.ToArray();
42+
var itemProperty = callExpression.Method.DeclaringType.GetProperty("Item", valueParameter.Type, parameterTypes);
43+
44+
assignment = Expression.Call(callExpression.Object, itemProperty.SetMethod, callExpression.Arguments.Concat(new[] { valueParameter }));
45+
}
46+
else
47+
{
48+
assignment = Expression.Assign(getterExpression.Body, valueParameter);
49+
}
50+
51+
var parameters = getterExpression.Parameters.Concat(new[] {valueParameter}).ToArray();
52+
return Expression.Lambda(assignment, parameters);
53+
}
54+
}
55+
}

src/WorkflowCore/Services/WorkflowExecutor.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.Extensions.DependencyInjection;
99
using WorkflowCore.Interface;
1010
using WorkflowCore.Models;
11+
using WorkflowCore.Services.FluentBuilders;
1112

1213
namespace WorkflowCore.Services
1314
{
@@ -110,7 +111,7 @@ public async Task<WorkflowExecutorResult> Execute(WorkflowInstance workflow)
110111

111112
if (result.Proceed)
112113
{
113-
ProcessOutputs(workflow, step, body);
114+
ProcessOutputs(workflow, step, body, context);
114115
}
115116

116117
_executionResultProcessor.ProcessExecutionResult(workflow, def, pointer, step, result, wfResult);
@@ -175,16 +176,23 @@ private void ProcessInputs(WorkflowInstance workflow, WorkflowStep step, IStepBo
175176
}
176177
}
177178

178-
private void ProcessOutputs(WorkflowInstance workflow, WorkflowStep step, IStepBody body)
179+
private void ProcessOutputs(WorkflowInstance workflow, WorkflowStep step, IStepBody body, IStepExecutionContext context)
179180
{
180181
foreach (var output in step.Outputs)
181182
{
182-
var member = (output.Target.Body as MemberExpression);
183183
var resolvedValue = output.Source.Compile().DynamicInvoke(body);
184184
var data = workflow.Data;
185-
var property = data.GetType().GetProperty(member.Member.Name);
186-
var convertedValue = Convert.ChangeType(resolvedValue, property.PropertyType);
187-
property.SetValue(data, convertedValue);
185+
var setter = ExpressionHelpers.CreateSetter(output.Target);
186+
var convertedValue = Convert.ChangeType(resolvedValue, setter.Parameters.Last().Type);
187+
188+
if (setter.Parameters.Count == 2)
189+
{
190+
setter.Compile().DynamicInvoke(data, convertedValue);
191+
}
192+
else
193+
{
194+
setter.Compile().DynamicInvoke(data, context, convertedValue);
195+
}
188196
}
189197
}
190198

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using WorkflowCore.Interface;
4+
using WorkflowCore.Models;
5+
using WorkflowCore.Sample03.Steps;
6+
7+
namespace WorkflowCore.Sample03
8+
{
9+
public class PassingDataWorkflow2 : IWorkflow<Dictionary<string, int>>
10+
{
11+
public void Build(IWorkflowBuilder<Dictionary<string, int>> builder)
12+
{
13+
builder
14+
.StartWith(context =>
15+
{
16+
Console.WriteLine("Starting workflow...");
17+
return ExecutionResult.Next();
18+
})
19+
.Then<AddNumbers>()
20+
.Input(step => step.Input1, data => data["Value1"])
21+
.Input(step => step.Input2, data => data["Value2"])
22+
.Output(data => data["Value3"], step => step.Output)
23+
.Then<CustomMessage>()
24+
.Name("Print custom message")
25+
.Input(step => step.Message, data => "The answer is " + data["Value3"].ToString())
26+
.Then(context =>
27+
{
28+
Console.WriteLine("Workflow complete");
29+
return ExecutionResult.Next();
30+
});
31+
}
32+
33+
public string Id => "PassingDataWorkflow2";
34+
35+
public int Version => 1;
36+
37+
}
38+
}

src/samples/WorkflowCore.Sample03/Program.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ public static void Main(string[] args)
2929

3030
host.StartWorkflow("PassingDataWorkflow", 1, initialData);
3131

32+
host.RegisterWorkflow<PassingDataWorkflow2, Dictionary<string, int>>();
33+
//host.Start();
34+
35+
var initialData2 = new Dictionary<string, int>
36+
{
37+
["Value1"] = 2,
38+
["Value2"] = 3
39+
};
40+
41+
host.StartWorkflow("PassingDataWorkflow2", 1, initialData2);
42+
3243
Console.ReadLine();
3344
host.Stop();
3445
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using FluentAssertions;
4+
using WorkflowCore.Interface;
5+
using WorkflowCore.Models;
6+
using WorkflowCore.Testing;
7+
using Xunit;
8+
9+
namespace WorkflowCore.IntegrationTests.Scenarios
10+
{
11+
public class DynamicDataIOScenario : WorkflowTest<DynamicDataIOScenario.DataIOWorkflow, DynamicDataIOScenario.MyDataClass>
12+
{
13+
public class AddNumbers : StepBody
14+
{
15+
public int Input1 { get; set; }
16+
public int Input2 { get; set; }
17+
public int Output { get; set; }
18+
19+
public override ExecutionResult Run(IStepExecutionContext context)
20+
{
21+
Output = (Input1 + Input2);
22+
return ExecutionResult.Next();
23+
}
24+
}
25+
26+
public class MyDataClass
27+
{
28+
public int Value1 { get; set; }
29+
public int Value2 { get; set; }
30+
public Dictionary<string, int> Storage { get; set; } = new Dictionary<string, int>();
31+
32+
public int this[string propertyName]
33+
{
34+
get => Storage[propertyName];
35+
set => Storage[propertyName] = value;
36+
}
37+
}
38+
39+
public class DataIOWorkflow : IWorkflow<MyDataClass>
40+
{
41+
public string Id => "DataIOWorkflow";
42+
public int Version => 1;
43+
public void Build(IWorkflowBuilder<MyDataClass> builder)
44+
{
45+
builder
46+
.StartWith<AddNumbers>()
47+
.Input(step => step.Input1, data => data.Value1)
48+
.Input(step => step.Input2, data => data.Value2)
49+
.Output(data => data["Value3"], step => step.Output);
50+
}
51+
}
52+
53+
public DynamicDataIOScenario()
54+
{
55+
Setup();
56+
}
57+
58+
[Fact]
59+
public void Scenario()
60+
{
61+
var workflowId = StartWorkflow(new MyDataClass() { Value1 = 2, Value2 = 3 });
62+
WaitForWorkflowToComplete(workflowId, TimeSpan.FromSeconds(30));
63+
64+
GetStatus(workflowId).Should().Be(WorkflowStatus.Complete);
65+
UnhandledStepErrors.Count.Should().Be(0);
66+
GetData(workflowId)["Value3"].Should().Be(5);
67+
}
68+
}
69+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
3+
namespace WorkflowCore.TestAssets.DataTypes
4+
{
5+
public class DynamicData
6+
{
7+
public Dictionary<string, object> Storage { get; set; } = new Dictionary<string, object>();
8+
9+
public object this[string propertyName]
10+
{
11+
get => Storage.TryGetValue(propertyName, out var value) ? value : null;
12+
set => Storage[propertyName] = value;
13+
}
14+
}
15+
}

test/WorkflowCore.TestAssets/Utils.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ public static string GetTestDefinitionJson()
2323
//return Properties.Resources.ResourceManager.GetString("stored_definition");
2424
return File.ReadAllText("stored-definition.json");
2525
}
26+
27+
public static string GetTestDefinitionDynamicJson()
28+
{
29+
return File.ReadAllText("stored-dynamic-definition.json");
30+
}
2631
}
2732
}
2833

test/WorkflowCore.TestAssets/WorkflowCore.TestAssets.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313

1414
<ItemGroup>
1515
<None Remove="stored-definition.json" />
16+
<None Remove="stored-dynamic-definition.json" />
1617
</ItemGroup>
1718

1819
<ItemGroup>
20+
<EmbeddedResource Include="stored-dynamic-definition.json">
21+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
22+
</EmbeddedResource>
1923
<EmbeddedResource Include="stored-definition.json">
2024
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
2125
</EmbeddedResource>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"Id": "Test",
3+
"Version": 1,
4+
"Description": "",
5+
"DataType": "WorkflowCore.TestAssets.DataTypes.DynamicData, WorkflowCore.TestAssets",
6+
"Steps": [
7+
{
8+
"Id": "Step1",
9+
"StepType": "WorkflowCore.TestAssets.Steps.Counter, WorkflowCore.TestAssets",
10+
"Inputs": { "Value": "data[\"Counter1\"]" },
11+
"Outputs": { "Counter1": "step.Value" },
12+
"NextStepId": "Step2"
13+
},
14+
{
15+
"Id": "Step2",
16+
"StepType": "WorkflowCore.TestAssets.Steps.Counter, WorkflowCore.TestAssets",
17+
"Inputs": { "Value": "data[\"Counter2\"]" },
18+
"Outputs": { "Counter2": "step.Value" },
19+
"NextStepId": "Step3"
20+
},
21+
{
22+
"Id": "Step3",
23+
"StepType": "WorkflowCore.Primitives.If, WorkflowCore",
24+
"NextStepId": "Step4",
25+
"Inputs": { "Condition": "object.Equals(data[\"Flag1\"], true)" },
26+
"Do": [
27+
[
28+
{
29+
"Id": "Step3.1.1",
30+
"StepType": "WorkflowCore.TestAssets.Steps.Counter, WorkflowCore.TestAssets",
31+
"Inputs": { "Value": "data[\"Counter3\"]" },
32+
"Outputs": { "Counter3": "step.Value" },
33+
"NextStepId": "Step3.1.2"
34+
},
35+
{
36+
"Id": "Step3.1.2",
37+
"StepType": "WorkflowCore.TestAssets.Steps.Counter, WorkflowCore.TestAssets",
38+
"Inputs": { "Value": "data[\"Counter4\"]" },
39+
"Outputs": { "Counter4": "step.Value" }
40+
}
41+
],
42+
[
43+
{
44+
"Id": "Step3.2.1",
45+
"StepType": "WorkflowCore.Primitives.WaitFor, WorkflowCore",
46+
"NextStepId": "Step3.2.2",
47+
"CancelCondition": "object.Equals(data[\"Flag2\"], true)",
48+
"Inputs": {
49+
"EventName": "\"Event1\"",
50+
"EventKey": "\"Key1\"",
51+
"EffectiveDate": "DateTime.Now"
52+
}
53+
},
54+
{
55+
"Id": "Step3.2.2",
56+
"StepType": "WorkflowCore.TestAssets.Steps.Counter, WorkflowCore.TestAssets",
57+
"Inputs": { "Value": "data[\"Counter5\"]" },
58+
"Outputs": { "Counter5": "step.Value" }
59+
}
60+
]
61+
]
62+
},
63+
{
64+
"Id": "Step4",
65+
"StepType": "WorkflowCore.TestAssets.Steps.Counter, WorkflowCore.TestAssets",
66+
"Inputs": { "Value": "data[\"Counter6\"]" },
67+
"Outputs": { "Counter6": "step.Value" }
68+
}
69+
]
70+
}

0 commit comments

Comments
 (0)