Skip to content

Commit c182dce

Browse files
authored
Target netstandard2, Yaml support, object graph support (#328)
1 parent d161f84 commit c182dce

File tree

40 files changed

+475
-311
lines changed

40 files changed

+475
-311
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ public class MyWorkflow : IWorkflow
2525
}
2626
```
2727

28-
## JSON Workflow Definitions
28+
## JSON / YAML Workflow Definitions
2929

30-
Define your workflows in JSON
30+
Define your workflows in JSON or YAML
3131

3232
```json
3333
{
@@ -47,6 +47,17 @@ Define your workflows in JSON
4747
}
4848
```
4949

50+
```yaml
51+
Id: HelloWorld
52+
Version: 1
53+
Steps:
54+
- Id: Hello
55+
StepType: MyApp.HelloWorld, MyApp
56+
NextStepId: Bye
57+
- Id: Bye
58+
StepType: MyApp.GoodbyeWorld, MyApp
59+
```
60+
5061
### Sample use cases
5162
5263
* New user workflow

ReleaseNotes/2.0.0.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Workflow Core 2.0.0
2+
3+
### Upgrade notes
4+
Existing JSON definitions will be loaded as follows
5+
```c#
6+
using WorkflowCore.Services.DefinitionStorage;
7+
...
8+
DefinitionLoader.LoadDefinition(json, Deserializers.Json);
9+
```
10+
11+
12+
* Targets .NET Standard 2.0
13+
14+
The core library now targets .NET Standard 2.0, in order to leverage newer features.
15+
16+
* Support for YAML definitions
17+
18+
Added support for YAML workflow definitions, which can be loaded as follows
19+
```c#
20+
using WorkflowCore.Services.DefinitionStorage;
21+
...
22+
DefinitionLoader.LoadDefinition(json, Deserializers.Yaml);
23+
```
24+
25+
Existing JSON definitions will be loaded as follows
26+
```c#
27+
using WorkflowCore.Services.DefinitionStorage;
28+
...
29+
DefinitionLoader.LoadDefinition(json, Deserializers.Json);
30+
```
31+
32+
* Object graphs and inline expressions on input properties
33+
34+
You can now pass object graphs to step inputs as opposed to just scalar values
35+
```
36+
"inputs":
37+
{
38+
"Body": {
39+
"Value1": 1,
40+
"Value2": 2
41+
},
42+
"Headers": {
43+
"Content-Type": "application/json"
44+
}
45+
},
46+
```
47+
If you want to evaluate an expression for a given property of your object, simply prepend and `@` and pass an expression string
48+
```
49+
"inputs":
50+
{
51+
"Body": {
52+
"@Value1": "data.MyValue * 2",
53+
"Value2": 5
54+
},
55+
"Headers": {
56+
"Content-Type": "application/json"
57+
}
58+
},
59+
```
60+
61+
* Support for enum values on input properties
62+
63+
If your step has an enum property, you can now just pass the string representation of the enum value and it will be automatically converted.
64+
65+
* Environment variables available in input expressions
66+
67+
You can now access environment variables from within input expressions.
68+
usage:
69+
```
70+
environment["VARIABLE_NAME"]
71+
```
72+

WorkflowCore.sln

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.Po
4040
EndProject
4141
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.Sqlite", "src\providers\WorkflowCore.Persistence.Sqlite\WorkflowCore.Persistence.Sqlite.csproj", "{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}"
4242
EndProject
43-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.LockProviders.Redlock", "src\providers\WorkflowCore.LockProviders.Redlock\WorkflowCore.LockProviders.Redlock.csproj", "{05250D58-A59E-4212-8D55-E7BC0396E9F5}"
44-
EndProject
4543
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.QueueProviders.RabbitMQ", "src\providers\WorkflowCore.QueueProviders.RabbitMQ\WorkflowCore.QueueProviders.RabbitMQ.csproj", "{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}"
4644
EndProject
4745
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample06", "src\samples\WorkflowCore.Sample06\WorkflowCore.Sample06.csproj", "{8FEAFD74-C304-4F75-BA38-4686BE55C891}"
@@ -100,6 +98,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReleaseNotes", "ReleaseNote
10098
ReleaseNotes\1.9.0.md = ReleaseNotes\1.9.0.md
10199
ReleaseNotes\1.9.2.md = ReleaseNotes\1.9.2.md
102100
ReleaseNotes\1.9.3.md = ReleaseNotes\1.9.3.md
101+
ReleaseNotes\2.0.0.md = ReleaseNotes\2.0.0.md
103102
EndProjectSection
104103
EndProject
105104
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample14", "src\samples\WorkflowCore.Sample14\WorkflowCore.Sample14.csproj", "{6BC66637-B42A-4334-ADFB-DBEC9F29D293}"
@@ -114,8 +113,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample15", "sr
114113
EndProject
115114
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample16", "src\samples\WorkflowCore.Sample16\WorkflowCore.Sample16.csproj", "{0C9617A9-C8B7-45F6-A54A-261A23AC881B}"
116115
EndProject
117-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScratchPad", "test\ScratchPad\ScratchPad.csproj", "{6396453F-4D0E-4CD4-BC89-87E8970F2A80}"
118-
EndProject
119116
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample17", "src\samples\WorkflowCore.Sample17\WorkflowCore.Sample17.csproj", "{42F475BC-95F4-42E1-8CCD-7B9C27487E33}"
120117
EndProject
121118
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.QueueProviders.SqlServer", "src\providers\WorkflowCore.QueueProviders.SqlServer\WorkflowCore.QueueProviders.SqlServer.csproj", "{7EDD9353-F5C2-414C-AE51-4B0F1C5E105A}"
@@ -186,10 +183,6 @@ Global
186183
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}.Debug|Any CPU.Build.0 = Debug|Any CPU
187184
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}.Release|Any CPU.ActiveCfg = Release|Any CPU
188185
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}.Release|Any CPU.Build.0 = Release|Any CPU
189-
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
190-
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
191-
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
192-
{05250D58-A59E-4212-8D55-E7BC0396E9F5}.Release|Any CPU.Build.0 = Release|Any CPU
193186
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
194187
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}.Debug|Any CPU.Build.0 = Debug|Any CPU
195188
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -294,10 +287,6 @@ Global
294287
{0C9617A9-C8B7-45F6-A54A-261A23AC881B}.Debug|Any CPU.Build.0 = Debug|Any CPU
295288
{0C9617A9-C8B7-45F6-A54A-261A23AC881B}.Release|Any CPU.ActiveCfg = Release|Any CPU
296289
{0C9617A9-C8B7-45F6-A54A-261A23AC881B}.Release|Any CPU.Build.0 = Release|Any CPU
297-
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
298-
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Debug|Any CPU.Build.0 = Debug|Any CPU
299-
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Release|Any CPU.ActiveCfg = Release|Any CPU
300-
{6396453F-4D0E-4CD4-BC89-87E8970F2A80}.Release|Any CPU.Build.0 = Release|Any CPU
301290
{42F475BC-95F4-42E1-8CCD-7B9C27487E33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
302291
{42F475BC-95F4-42E1-8CCD-7B9C27487E33}.Debug|Any CPU.Build.0 = Debug|Any CPU
303292
{42F475BC-95F4-42E1-8CCD-7B9C27487E33}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -357,7 +346,6 @@ Global
357346
{1DE96D4F-F2CA-4740-8764-BADD1000040A} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
358347
{9274B938-3996-4FBA-AE2F-0C82009B1116} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
359348
{86BC1E05-E9CE-4E53-B324-885A2FDBCE74} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
360-
{05250D58-A59E-4212-8D55-E7BC0396E9F5} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
361349
{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
362350
{8FEAFD74-C304-4F75-BA38-4686BE55C891} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
363351
{37B598A8-B054-4ABA-884D-96AEF2511600} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
@@ -384,7 +372,6 @@ Global
384372
{EC497168-5347-4E70-9D9E-9C2F826C1CDF} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
385373
{9B7811AC-68D6-4D19-B1E9-65423393ED83} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
386374
{0C9617A9-C8B7-45F6-A54A-261A23AC881B} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
387-
{6396453F-4D0E-4CD4-BC89-87E8970F2A80} = {E6CEAD8D-F565-471E-A0DC-676F54EAEDEB}
388375
{42F475BC-95F4-42E1-8CCD-7B9C27487E33} = {5080DB09-CBE8-4C45-9957-C3BB7651755E}
389376
{7EDD9353-F5C2-414C-AE51-4B0F1C5E105A} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
390377
{5E82A137-0954-46A1-8C46-13C00F0E4842} = {2EEE6ABD-EE9B-473F-AF2D-6DABB85D7BA2}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
using WorkflowCore.Models;
1+
using System;
2+
using WorkflowCore.Models;
3+
using WorkflowCore.Models.DefinitionStorage.v1;
24

35
namespace WorkflowCore.Interface
46
{
57
public interface IDefinitionLoader
68
{
7-
WorkflowDefinition LoadDefinition(string json);
9+
WorkflowDefinition LoadDefinition(string source, Func<string, DefinitionSourceV1> deserializer);
810
}
911
}

src/WorkflowCore/Models/DefinitionStorage/v1/StepSourceV1.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Dynamic;
34
using System.Text;
45

56
namespace WorkflowCore.Models.DefinitionStorage.v1
@@ -26,7 +27,7 @@ public class StepSourceV1
2627

2728
public string NextStepId { get; set; }
2829

29-
public Dictionary<string, string> Inputs { get; set; } = new Dictionary<string, string>();
30+
public ExpandoObject Inputs { get; set; } = new ExpandoObject();
3031

3132
public Dictionary<string, string> Outputs { get; set; } = new Dictionary<string, string>();
3233

src/WorkflowCore/Services/DefinitionStorage/DefinitionLoader.cs

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using Newtonsoft.Json;
22
using System;
3+
using System.Collections;
34
using System.Collections.Generic;
45
using System.Linq;
56
using System.Linq.Dynamic.Core;
67
using System.Linq.Expressions;
78
using System.Reflection;
89
using System.Text;
10+
using Newtonsoft.Json.Linq;
911
using WorkflowCore.Interface;
1012
using WorkflowCore.Models;
1113
using WorkflowCore.Primitives;
@@ -24,10 +26,10 @@ public DefinitionLoader(IWorkflowRegistry registry)
2426
_registry = registry;
2527
}
2628

27-
public WorkflowDefinition LoadDefinition(string json)
29+
public WorkflowDefinition LoadDefinition(string source, Func<string, DefinitionSourceV1> deserializer)
2830
{
29-
var source = JsonConvert.DeserializeObject<DefinitionSourceV1>(json);
30-
var def = Convert(source);
31+
var sourceObj = deserializer(source);
32+
var def = Convert(sourceObj);
3133
_registry.RegisterWorkflow(def);
3234
return def;
3335
}
@@ -171,13 +173,24 @@ private void AttachInputs(StepSourceV1 source, Type dataType, Type stepType, Wor
171173
{
172174
var dataParameter = Expression.Parameter(dataType, "data");
173175
var contextParameter = Expression.Parameter(typeof(IStepExecutionContext), "context");
174-
var sourceExpr = DynamicExpressionParser.ParseLambda(new [] { dataParameter, contextParameter }, typeof(object), input.Value);
176+
var environmentVarsParameter = Expression.Parameter(typeof(IDictionary), "environment");
177+
var stepProperty = stepType.GetProperty(input.Key);
175178

176-
var stepParameter = Expression.Parameter(stepType, "step");
177-
var targetProperty = Expression.Property(stepParameter, input.Key);
178-
var targetExpr = Expression.Lambda(targetProperty, stepParameter);
179+
if (input.Value is string)
180+
{
181+
var acn = BuildScalarInputAction(input, dataParameter, contextParameter, environmentVarsParameter, stepProperty);
182+
step.Inputs.Add(new ActionParameter<IStepBody, object>(acn));
183+
continue;
184+
}
179185

180-
step.Inputs.Add(new MemberMapParameter(sourceExpr, targetExpr));
186+
if ((input.Value is IDictionary<string, object>) || (input.Value is IDictionary<object, object>))
187+
{
188+
var acn = BuildObjectInputAction(input, dataParameter, contextParameter, environmentVarsParameter, stepProperty);
189+
step.Inputs.Add(new ActionParameter<IStepBody, object>(acn));
190+
continue;
191+
}
192+
193+
throw new ArgumentException($"Unknown type for input {input.Key} on {source.Id}");
181194
}
182195
}
183196

@@ -221,5 +234,52 @@ private Type FindType(string name)
221234
return Type.GetType(name, true, true);
222235
}
223236

237+
private static Action<IStepBody, object, IStepExecutionContext> BuildScalarInputAction(KeyValuePair<string, object> input, ParameterExpression dataParameter, ParameterExpression contextParameter, ParameterExpression environmentVarsParameter, PropertyInfo stepProperty)
238+
{
239+
var expr = System.Convert.ToString(input.Value);
240+
var sourceExpr = DynamicExpressionParser.ParseLambda(new[] { dataParameter, contextParameter, environmentVarsParameter }, typeof(object), expr);
241+
242+
void acn(IStepBody pStep, object pData, IStepExecutionContext pContext)
243+
{
244+
object resolvedValue = sourceExpr.Compile().DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables());
245+
if (stepProperty.PropertyType.IsEnum)
246+
stepProperty.SetValue(pStep, Enum.Parse(stepProperty.PropertyType, (string)resolvedValue, true));
247+
else
248+
stepProperty.SetValue(pStep, System.Convert.ChangeType(resolvedValue, stepProperty.PropertyType));
249+
}
250+
return acn;
251+
}
252+
253+
private static Action<IStepBody, object, IStepExecutionContext> BuildObjectInputAction(KeyValuePair<string, object> input, ParameterExpression dataParameter, ParameterExpression contextParameter, ParameterExpression environmentVarsParameter, PropertyInfo stepProperty)
254+
{
255+
void acn(IStepBody pStep, object pData, IStepExecutionContext pContext)
256+
{
257+
var stack = new Stack<JObject>();
258+
var destObj = JObject.FromObject(input.Value);
259+
stack.Push(destObj);
260+
261+
while (stack.Count > 0)
262+
{
263+
var subobj = stack.Pop();
264+
foreach (var prop in subobj.Properties().ToList())
265+
{
266+
if (prop.Name.StartsWith("@"))
267+
{
268+
var sourceExpr = DynamicExpressionParser.ParseLambda(new[] { dataParameter, contextParameter, environmentVarsParameter }, typeof(object), prop.Value.ToString());
269+
object resolvedValue = sourceExpr.Compile().DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables());
270+
subobj.Remove(prop.Name);
271+
subobj.Add(prop.Name.TrimStart('@'), JToken.FromObject(resolvedValue));
272+
}
273+
}
274+
275+
foreach (var child in subobj.Children<JObject>())
276+
stack.Push(child);
277+
}
278+
279+
stepProperty.SetValue(pStep, destObj);
280+
}
281+
return acn;
282+
}
283+
224284
}
225285
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
using SharpYaml.Serialization;
4+
using WorkflowCore.Models.DefinitionStorage.v1;
5+
6+
namespace WorkflowCore.Services.DefinitionStorage
7+
{
8+
public static class Deserializers
9+
{
10+
private static Serializer yamlSerializer = new Serializer();
11+
12+
public static Func<string, DefinitionSourceV1> Json = (source) => JsonConvert.DeserializeObject<DefinitionSourceV1>(source);
13+
14+
public static Func<string, DefinitionSourceV1> Yaml = (source) => yamlSerializer.DeserializeInto(source, new DefinitionSourceV1());
15+
}
16+
}

src/WorkflowCore/WorkflowCore.csproj

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<AssemblyTitle>Workflow Core</AssemblyTitle>
55
<Authors>Daniel Gerlag</Authors>
6-
<TargetFramework>netstandard1.3</TargetFramework>
6+
<TargetFramework>netstandard2.0</TargetFramework>
77
<AssemblyName>WorkflowCore</AssemblyName>
88
<PackageId>WorkflowCore</PackageId>
99
<PackageTags>workflow;.NET;Core;state machine</PackageTags>
@@ -15,33 +15,26 @@
1515
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
1616
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
1717
<Description>Workflow Core is a light weight workflow engine targeting .NET Standard.</Description>
18-
<Version>1.9.4</Version>
19-
<AssemblyVersion>1.9.4.0</AssemblyVersion>
20-
<FileVersion>1.9.4.0</FileVersion>
18+
<Version>2.0.0</Version>
19+
<AssemblyVersion>2.0.0.0</AssemblyVersion>
20+
<FileVersion>2.0.0.0</FileVersion>
2121
<PackageReleaseNotes></PackageReleaseNotes>
2222
<PackageIconUrl>https://github.com/danielgerlag/workflow-core/raw/master/src/logo.png</PackageIconUrl>
2323
</PropertyGroup>
2424

2525
<ItemGroup>
26-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
27-
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
28-
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="1.1.1" />
26+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.0" />
27+
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.0" />
28+
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="2.2.0" />
2929
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
30+
<PackageReference Include="SharpYaml" Version="1.6.5" />
3031
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.7.12" />
3132
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
3233
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
33-
</ItemGroup>
34-
35-
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
3634
<PackageReference Include="System.Reflection" Version="4.3.0" />
3735
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.3.0" />
3836
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
3937
<PackageReference Include="System.Linq.Queryable" Version="4.3.0" />
4038
</ItemGroup>
4139

42-
<ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
43-
<Reference Include="System" />
44-
<Reference Include="Microsoft.CSharp" />
45-
</ItemGroup>
46-
4740
</Project>

0 commit comments

Comments
 (0)