Skip to content

Commit 87d52b7

Browse files
authored
Merge pull request #762 from MrZoidberg/issue-761-DataObjectSerializer-refactoring
Refactored DataObjectSerializer to be able to use decimal value types and BsonRepresentation attributes
2 parents 299dd37 + 96e5e66 commit 87d52b7

File tree

7 files changed

+150
-18
lines changed

7 files changed

+150
-18
lines changed

src/providers/WorkflowCore.Persistence.MongoDB/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,35 @@ Use the .UseMongoDB extension method when building your service provider.
1717
```C#
1818
services.AddWorkflow(x => x.UseMongoDB(@"mongodb://localhost:27017", "workflow"));
1919
```
20+
21+
### State object serialization
22+
23+
By default (to maintain backwards compatibility), the state object is serialized using a two step serialization process using object -> JSON -> BSON serialization.
24+
This approach has some limitations, for example you cannot control which types will be used in MongoDB for particular fields and you cannot use basic types that are not present in JSON (decimal, timestamp, etc).
25+
26+
To eliminate these limitations, you can use a direct object -> BSON serialization and utilize all serialization possibilities that MongoDb driver provides. You can read more in the [MongoDb CSharp documentation](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/).
27+
To enable direct serilization you need to register a class map for you state class somewhere in your startup process before you run `WorkflowHost`.
28+
29+
```C#
30+
private void RunWorkflow()
31+
{
32+
var host = this._serviceProvider.GetService<IWorkflowHost>();
33+
if (host == null)
34+
{
35+
return;
36+
}
37+
38+
if (!BsonClassMap.IsClassMapRegistered(typeof(MyWorkflowState)))
39+
{
40+
BsonClassMap.RegisterClassMap<MyWorkflowState>(cm =>
41+
{
42+
cm.AutoMap();
43+
});
44+
}
45+
46+
host.RegisterWorkflow<MyWorkflow, MyWorkflowState>();
47+
48+
host.Start();
49+
}
50+
51+
```

src/providers/WorkflowCore.Persistence.MongoDB/Services/DataObjectSerializer.cs

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
using MongoDB.Bson.Serialization.Serializers;
33
using MongoDB.Bson;
44
using System;
5+
using System.Collections;
56
using System.Collections.Generic;
67
using System.Linq;
7-
using System.Linq.Expressions;
8-
using System.Threading.Tasks;
9-
using WorkflowCore.Models;
108
using Newtonsoft.Json;
119

1210
namespace WorkflowCore.Persistence.MongoDB.Services
@@ -17,7 +15,7 @@ public class DataObjectSerializer : SerializerBase<object>
1715
{
1816
TypeNameHandling = TypeNameHandling.Objects,
1917
};
20-
18+
2119
public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
2220
{
2321
if (context.Reader.CurrentBsonType == BsonType.String)
@@ -26,18 +24,103 @@ public override object Deserialize(BsonDeserializationContext context, BsonDeser
2624
return JsonConvert.DeserializeObject(raw, SerializerSettings);
2725
}
2826

29-
return BsonSerializer.Deserialize(context.Reader, typeof(object));
27+
var obj = BsonSerializer.Deserialize(context.Reader, typeof(object));
28+
return obj;
3029
}
3130

3231
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
3332
{
34-
var str = JsonConvert.SerializeObject(value, SerializerSettings);
35-
var doc = BsonDocument.Parse(str);
36-
ConvertMetaFormat(doc);
33+
BsonDocument doc;
34+
if (BsonClassMap.IsClassMapRegistered(value.GetType()))
35+
{
36+
doc = value.ToBsonDocument();
37+
doc.Remove("_t");
38+
doc.InsertAt(0, new BsonElement("_t", value.GetType().AssemblyQualifiedName));
39+
AddTypeInformation(doc.Elements, value, string.Empty);
40+
}
41+
else
42+
{
43+
var str = JsonConvert.SerializeObject(value, SerializerSettings);
44+
doc = BsonDocument.Parse(str);
45+
ConvertMetaFormat(doc);
46+
}
3747

3848
BsonSerializer.Serialize(context.Writer, doc);
3949
}
4050

51+
private void AddTypeInformation(IEnumerable<BsonElement> elements, object value, string xPath)
52+
{
53+
foreach (var element in elements)
54+
{
55+
var elementXPath = string.IsNullOrEmpty(xPath) ? element.Name : xPath + "." + element.Name;
56+
if (element.Value.IsBsonDocument)
57+
{
58+
var doc = element.Value.AsBsonDocument;
59+
doc.Remove("_t");
60+
doc.InsertAt(0, new BsonElement("_t", GetTypeNameFromXPath(value, elementXPath)));
61+
AddTypeInformation(doc.Elements, value, elementXPath);
62+
}
63+
if (element.Value.IsBsonArray)
64+
{
65+
AddTypeInformation(element.Value.AsBsonArray, value, elementXPath);
66+
}
67+
}
68+
}
69+
70+
private string GetTypeNameFromXPath(object root, string xPath)
71+
{
72+
var parts = xPath.Split('.').ToList();
73+
object value = root;
74+
while (parts.Count > 0)
75+
{
76+
var subPath = parts[0];
77+
if (subPath[0] == '[')
78+
{
79+
var index = Int32.Parse(subPath.Trim('[', ']'));
80+
if ((value is IList) || value.GetType().IsArray)
81+
{
82+
IList list = (IList) value;
83+
value = list[index];
84+
}
85+
else
86+
{
87+
throw new NotSupportedException();
88+
}
89+
}
90+
else
91+
{
92+
var propInfo = value.GetType().GetProperty(subPath);
93+
value = propInfo.GetValue(value);
94+
}
95+
96+
parts.RemoveAt(0);
97+
}
98+
99+
return value.GetType().AssemblyQualifiedName;
100+
}
101+
102+
private void AddTypeInformation(IEnumerable<BsonValue> elements, object value, string xPath)
103+
{
104+
//foreach (var element in elements)
105+
for (int i = 0; i < elements.Count(); i++)
106+
{
107+
var element = elements.ElementAt(i);
108+
if (element.IsBsonDocument)
109+
{
110+
var doc = element.AsBsonDocument;
111+
var elementXPath = xPath + $".[{i}]";
112+
doc.Remove("_t");
113+
doc.InsertAt(0, new BsonElement("_t", GetTypeNameFromXPath(value, elementXPath)));
114+
AddTypeInformation(doc.Elements, value, elementXPath);
115+
}
116+
117+
if (element.IsBsonArray)
118+
{
119+
AddTypeInformation(element.AsBsonArray, value, xPath);
120+
}
121+
}
122+
}
123+
41124
private static void ConvertMetaFormat(BsonDocument root)
42125
{
43126
var stack = new Stack<BsonDocument>();
@@ -72,4 +155,4 @@ private static void ConvertMetaFormat(BsonDocument root)
72155
}
73156
}
74157
}
75-
}
158+
}

src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
1515
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
1616
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
17-
<Version>3.0.3</Version>
17+
<Version>3.0.4</Version>
1818
<Description>Provides support to persist workflows running on Workflow Core to a MongoDB database.</Description>
19-
<AssemblyVersion>3.0.3.0</AssemblyVersion>
20-
<FileVersion>3.0.3.0</FileVersion>
21-
<PackageVersion>3.0.3</PackageVersion>
19+
<AssemblyVersion>3.0.4.0</AssemblyVersion>
20+
<FileVersion>3.0.4.0</FileVersion>
21+
<PackageVersion>3.0.4</PackageVersion>
2222
</PropertyGroup>
2323

2424
<ItemGroup>

src/samples/WorkflowCore.TestSample01/NUnitTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ namespace WorkflowCore.TestSample01
1313
public class NUnitTest : WorkflowTest<MyWorkflow, MyDataClass>
1414
{
1515
[SetUp]
16-
protected override void Setup()
16+
protected override void Setup(bool registerClassMap = false)
1717
{
18-
base.Setup();
18+
base.Setup(registerClassMap);
1919
}
2020

2121
[Test]

test/WorkflowCore.IntegrationTests/Scenarios/DataIOScenario.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public class MyDataClass
2929
public int Value1 { get; set; }
3030
public int Value2 { get; set; }
3131
public int Value3 { get; set; }
32+
public decimal Value4 { get; set; }
33+
34+
public DataSubclass SubValue { get; set; }
35+
}
36+
37+
public class DataSubclass
38+
{
39+
public decimal Value5 { get; set; }
3240
}
3341

3442
public class DataIOWorkflow : IWorkflow<MyDataClass>
@@ -47,18 +55,20 @@ public void Build(IWorkflowBuilder<MyDataClass> builder)
4755

4856
public DataIOScenario()
4957
{
50-
Setup();
58+
Setup(true);
5159
}
5260

5361
[Fact]
5462
public void Scenario()
5563
{
56-
var workflowId = StartWorkflow(new MyDataClass() { Value1 = 2, Value2 = 3 });
64+
decimal v4 = 1.235465673450897890m;
65+
var workflowId = StartWorkflow(new MyDataClass() {Value1 = 2, Value2 = 3, Value4 = v4, SubValue = new DataSubclass() {Value5 = v4}});
5766
WaitForWorkflowToComplete(workflowId, TimeSpan.FromSeconds(30));
5867

5968
GetStatus(workflowId).Should().Be(WorkflowStatus.Complete);
6069
UnhandledStepErrors.Count.Should().Be(0);
6170
GetData(workflowId).Value3.Should().Be(5);
71+
GetData(workflowId).Value4.Should().Be(v4);
6272
}
6373
}
6474
}

test/WorkflowCore.Testing/WorkflowCore.Testing.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
1313
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.0" />
1414
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.0" />
15+
<PackageReference Include="MongoDB.Bson" Version="2.8.1" />
1516
</ItemGroup>
1617

1718
<ItemGroup>

test/WorkflowCore.Testing/WorkflowTest.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Threading.Tasks;
88
using Microsoft.Extensions.DependencyInjection;
99
using Microsoft.Extensions.Logging;
10+
using MongoDB.Bson.Serialization;
1011
using WorkflowCore.Interface;
1112
using WorkflowCore.Models;
1213

@@ -20,7 +21,7 @@ public abstract class WorkflowTest<TWorkflow, TData> : IDisposable
2021
protected IPersistenceProvider PersistenceProvider;
2122
protected List<StepError> UnhandledStepErrors = new List<StepError>();
2223

23-
protected virtual void Setup()
24+
protected virtual void Setup(bool registerClassMap = false)
2425
{
2526
//setup dependency injection
2627
IServiceCollection services = new ServiceCollection();
@@ -33,6 +34,11 @@ protected virtual void Setup()
3334
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
3435
//loggerFactory.AddConsole(LogLevel.Debug);
3536

37+
if (registerClassMap && !BsonClassMap.IsClassMapRegistered(typeof(TData)))
38+
{
39+
BsonClassMap.RegisterClassMap<TData>(map => map.AutoMap());
40+
}
41+
3642
PersistenceProvider = serviceProvider.GetService<IPersistenceProvider>();
3743
Host = serviceProvider.GetService<IWorkflowHost>();
3844
Host.RegisterWorkflow<TWorkflow, TData>();

0 commit comments

Comments
 (0)