diff --git a/Fluxera.ValueObject.sln b/Fluxera.ValueObject.sln index fa7521a..cff34f9 100644 --- a/Fluxera.ValueObject.sln +++ b/Fluxera.ValueObject.sln @@ -27,6 +27,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.ValueObject", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.ValueObject.UnitTests", "tests\Fluxera.ValueObject.UnitTests\Fluxera.ValueObject.UnitTests.csproj", "{6F6CAF72-1D71-434B-AD09-7A9216669502}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.JsonNet", "src\Fluxera.ValueObject.JsonNet\Fluxera.ValueObject.JsonNet.csproj", "{FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.JsonNet.UnitTests", "tests\Fluxera.ValueObject.JsonNet.UnitTests\Fluxera.ValueObject.JsonNet.UnitTests.csproj", "{F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.SystemTextJson", "src\Fluxera.ValueObject.SystemTextJson\Fluxera.ValueObject.SystemTextJson.csproj", "{78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.SystemTextJson.UnitTests", "tests\Fluxera.ValueObject.SystemTextJson.UnitTests\Fluxera.ValueObject.SystemTextJson.UnitTests.csproj", "{4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.LiteDB", "src\Fluxera.ValueObject.LiteDB\Fluxera.ValueObject.LiteDB.csproj", "{ACAAD773-D974-41DA-A04C-9A6B06766D82}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.LiteDB.UnitTests", "tests\Fluxera.ValueObject.LiteDB.UnitTests\Fluxera.ValueObject.LiteDB.UnitTests.csproj", "{5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.MongoDB", "src\Fluxera.ValueObject.MongoDB\Fluxera.ValueObject.MongoDB.csproj", "{8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.MongoDB.UnitTests", "tests\Fluxera.ValueObject.MongoDB.UnitTests\Fluxera.ValueObject.MongoDB.UnitTests.csproj", "{75A3F24F-590F-457B-B031-B3A41FB51C02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.EntityFrameworkCore", "src\Fluxera.ValueObject.EntityFrameworkCore\Fluxera.ValueObject.EntityFrameworkCore.csproj", "{AD7A0B26-A63A-47DC-897B-781206B084AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fluxera.ValueObject.EntityFrameworkCore.UnitTests", "tests\Fluxera.ValueObject.EntityFrameworkCore.UnitTests\Fluxera.ValueObject.EntityFrameworkCore.UnitTests.csproj", "{1E08934F-F3FF-498A-B660-18E6C7C0958B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +61,46 @@ Global {6F6CAF72-1D71-434B-AD09-7A9216669502}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F6CAF72-1D71-434B-AD09-7A9216669502}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F6CAF72-1D71-434B-AD09-7A9216669502}.Release|Any CPU.Build.0 = Release|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Release|Any CPU.Build.0 = Release|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Release|Any CPU.Build.0 = Release|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Release|Any CPU.Build.0 = Release|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Release|Any CPU.Build.0 = Release|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Release|Any CPU.Build.0 = Release|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Release|Any CPU.Build.0 = Release|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Release|Any CPU.Build.0 = Release|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Release|Any CPU.Build.0 = Release|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Release|Any CPU.Build.0 = Release|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -48,6 +108,16 @@ Global GlobalSection(NestedProjects) = preSolution {0112DE42-7C32-4ECC-A5C1-6839C63A689C} = {DF28D730-99B3-4811-AAC7-725A73B3F668} {6F6CAF72-1D71-434B-AD09-7A9216669502} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {ACAAD773-D974-41DA-A04C-9A6B06766D82} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {75A3F24F-590F-457B-B031-B3A41FB51C02} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {AD7A0B26-A63A-47DC-897B-781206B084AC} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {1E08934F-F3FF-498A-B660-18E6C7C0958B} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D97BF2AF-6E68-4E88-BF70-773A86E26013} diff --git a/README.md b/README.md index 8fd2db2..97ee778 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [](https://dev.azure.com/fluxera/Foundation/_build/latest?definitionId=63&branchName=main) # Fluxera.ValueObject -A value objects library. +A value object implementation. This library helps in implementing **Value Object** classes in the context of **Domain-Driven Design**. A **Value Object** has several traits, some of which this library provides. diff --git a/src/Fluxera.ValueObject.EntityFrameworkCore/Fluxera.ValueObject.EntityFrameworkCore.csproj b/src/Fluxera.ValueObject.EntityFrameworkCore/Fluxera.ValueObject.EntityFrameworkCore.csproj new file mode 100644 index 0000000..939ac82 --- /dev/null +++ b/src/Fluxera.ValueObject.EntityFrameworkCore/Fluxera.ValueObject.EntityFrameworkCore.csproj @@ -0,0 +1,37 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + </PropertyGroup> + + <PropertyGroup> + <Title>Fluxera.ValueObject.EntityFrameworkCore</Title> + <Description>A libary that provides serializer support for EF Core for value objects.</Description> + <PackageTags>fluxera;library;ddd;value-object;ef-core</PackageTags> + </PropertyGroup> + + <ItemGroup> + <None Include="..\..\README.md" Link="Properties\README.md"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + <None Include="..\..\icon.png" Link="Properties\icon.png"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="GitVersion.MsBuild" Version="5.10.1"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Fluxera.ValueObject\Fluxera.ValueObject.csproj" /> + </ItemGroup> + +</Project> \ No newline at end of file diff --git a/src/Fluxera.ValueObject.EntityFrameworkCore/ModelBuilderExtensions.cs b/src/Fluxera.ValueObject.EntityFrameworkCore/ModelBuilderExtensions.cs new file mode 100644 index 0000000..7973213 --- /dev/null +++ b/src/Fluxera.ValueObject.EntityFrameworkCore/ModelBuilderExtensions.cs @@ -0,0 +1,53 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Fluxera.Guards; + using JetBrains.Annotations; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata; + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + + /// <summary> + /// Extension methods for the <see cref="ModelBuilder" /> type. + /// </summary> + [PublicAPI] + public static class ModelBuilderExtensions + { + /// <summary> + /// Configure the module builder to use the <see cref="PrimitiveValueObjectConverter{TValueObject,TValue}" />. + /// </summary> + /// <param name="modelBuilder"></param> + public static void UsePrimitiveValueObject(this ModelBuilder modelBuilder) + { + Guard.Against.Null(modelBuilder, nameof(modelBuilder)); + + foreach(IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) + { + IEnumerable<PropertyInfo> properties = entityType + .ClrType + .GetProperties() + .Where(type => type.PropertyType.IsPrimitiveValueObject()); + + foreach(PropertyInfo property in properties) + { + Type enumerationType = property.PropertyType; + Type valueType = enumerationType.GetValueType(); + + Type converterTypeTemplate = typeof(PrimitiveValueObjectConverter<,>); + + Type converterType = converterTypeTemplate.MakeGenericType(enumerationType, valueType); + + ValueConverter converter = (ValueConverter)Activator.CreateInstance(converterType); + + modelBuilder + .Entity(entityType.ClrType) + .Property(property.Name) + .HasConversion(converter); + } + } + } + } +} diff --git a/src/Fluxera.ValueObject.EntityFrameworkCore/PrimitiveValueObjectConverter.cs b/src/Fluxera.ValueObject.EntityFrameworkCore/PrimitiveValueObjectConverter.cs new file mode 100644 index 0000000..d641e39 --- /dev/null +++ b/src/Fluxera.ValueObject.EntityFrameworkCore/PrimitiveValueObjectConverter.cs @@ -0,0 +1,39 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore +{ + using System; + using System.Reflection; + using JetBrains.Annotations; + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + + /// <inheritdoc /> + [PublicAPI] + public sealed class PrimitiveValueObjectConverter<TValueObject, TValue> : ValueConverter<TValueObject, TValue> + where TValueObject : PrimitiveValueObject<TValueObject, TValue> + where TValue : IComparable + { + /// <summary> + /// Initializes a new instance of the <see cref="PrimitiveValueObjectConverter{TValueObject,TValue}" /> type. + /// </summary> + public PrimitiveValueObjectConverter() + : base(valueObject => Serialize(valueObject), value => Deserialize(value)) + { + } + + private static TValue Serialize(TValueObject valueObject) + { + TValue value = valueObject.Value; + return value; + } + + private static TValueObject Deserialize(TValue value) + { + if(value is null) + { + return null; + } + + object instance = Activator.CreateInstance(typeof(TValueObject), BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TValueObject)instance; + } + } +} diff --git a/src/Fluxera.ValueObject.JsonNet/CompositeContractResolver.cs b/src/Fluxera.ValueObject.JsonNet/CompositeContractResolver.cs new file mode 100644 index 0000000..d8aaa9b --- /dev/null +++ b/src/Fluxera.ValueObject.JsonNet/CompositeContractResolver.cs @@ -0,0 +1,56 @@ +namespace Fluxera.ValueObject.JsonNet +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using Fluxera.Guards; + using JetBrains.Annotations; + using Newtonsoft.Json.Serialization; + + /// <summary> + /// A <see cref="IContractResolver" /> that allows to have multiple other resolver instances added. + /// </summary> + [PublicAPI] + public sealed class CompositeContractResolver : IContractResolver, IEnumerable<IContractResolver> + { + private readonly IList<IContractResolver> contractResolvers = new List<IContractResolver>(); + private readonly DefaultContractResolver defaultContractResolver = new DefaultContractResolver(); + + /// <inheritdoc /> + public JsonContract ResolveContract(Type type) + { + return this.contractResolvers + .Select(x => x.ResolveContract(type)) + .FirstOrDefault(); + } + + /// <inheritdoc /> + public IEnumerator<IContractResolver> GetEnumerator() + { + return this.contractResolvers.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// <summary> + /// Add a resolver instance. + /// </summary> + /// <param name="contractResolver"></param> + public void Add(IContractResolver contractResolver) + { + Guard.Against.Null(contractResolver); + + if(this.contractResolvers.Contains(this.defaultContractResolver)) + { + this.contractResolvers.Remove(this.defaultContractResolver); + } + + this.contractResolvers.Add(contractResolver); + this.contractResolvers.Add(this.defaultContractResolver); + } + } +} diff --git a/src/Fluxera.ValueObject.JsonNet/Fluxera.ValueObject.JsonNet.csproj b/src/Fluxera.ValueObject.JsonNet/Fluxera.ValueObject.JsonNet.csproj new file mode 100644 index 0000000..7a6eacd --- /dev/null +++ b/src/Fluxera.ValueObject.JsonNet/Fluxera.ValueObject.JsonNet.csproj @@ -0,0 +1,37 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup> + <Title>Fluxera.ValueObject.JsonNet</Title> + <Description>A libary that provides serializer support for JSON.NET for value objects.</Description> + <PackageTags>fluxera;library;ddd;value-object;json</PackageTags> + </PropertyGroup> + + <ItemGroup> + <None Include="..\..\README.md" Link="Properties\README.md"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + <None Include="..\..\icon.png" Link="Properties\icon.png"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="GitVersion.MsBuild" Version="5.10.1"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Fluxera.ValueObject\Fluxera.ValueObject.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Fluxera.ValueObject.JsonNet/JsonSerializerSettingsExtensions.cs b/src/Fluxera.ValueObject.JsonNet/JsonSerializerSettingsExtensions.cs new file mode 100644 index 0000000..3250680 --- /dev/null +++ b/src/Fluxera.ValueObject.JsonNet/JsonSerializerSettingsExtensions.cs @@ -0,0 +1,22 @@ +namespace Fluxera.ValueObject.JsonNet +{ + using Newtonsoft.Json; + + /// <summary> + /// Extension methods for the <see cref="JsonSerializerSettings" /> type. + /// </summary> + public static class JsonSerializerSettingsExtensions + { + /// <summary> + /// Configure the serializer to use the <see cref="PrimitiveValueObjectConverter{TValueObject,TValue}" />. + /// </summary> + /// <param name="settings"></param> + public static void UsePrimitiveValueObject(this JsonSerializerSettings settings) + { + settings.ContractResolver = new CompositeContractResolver + { + new PrimitiveValueObjectContractResolver() + }; + } + } +} diff --git a/src/Fluxera.ValueObject.JsonNet/PrimitiveValueObjectContractResolver.cs b/src/Fluxera.ValueObject.JsonNet/PrimitiveValueObjectContractResolver.cs new file mode 100644 index 0000000..fd87fce --- /dev/null +++ b/src/Fluxera.ValueObject.JsonNet/PrimitiveValueObjectContractResolver.cs @@ -0,0 +1,27 @@ +namespace Fluxera.ValueObject.JsonNet +{ + using System; + using JetBrains.Annotations; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + + /// <inheritdoc /> + [PublicAPI] + public sealed class PrimitiveValueObjectContractResolver : DefaultContractResolver + { + /// <inheritdoc /> + protected override JsonConverter ResolveContractConverter(Type objectType) + { + if(objectType.IsPrimitiveValueObject()) + { + Type valueType = objectType.GetValueType(); + Type converterTypeTemplate = typeof(PrimitiveValueObjectConverter<,>); + Type converterType = converterTypeTemplate.MakeGenericType(objectType, valueType); + + return (JsonConverter)Activator.CreateInstance(converterType); + } + + return base.ResolveContractConverter(objectType); + } + } +} diff --git a/src/Fluxera.ValueObject.JsonNet/PrimitiveValueObjectConverter.cs b/src/Fluxera.ValueObject.JsonNet/PrimitiveValueObjectConverter.cs new file mode 100644 index 0000000..c857826 --- /dev/null +++ b/src/Fluxera.ValueObject.JsonNet/PrimitiveValueObjectConverter.cs @@ -0,0 +1,46 @@ +namespace Fluxera.ValueObject.JsonNet +{ + using System; + using System.Reflection; + using JetBrains.Annotations; + using Newtonsoft.Json; + + /// <inheritdoc /> + [PublicAPI] + public sealed class PrimitiveValueObjectConverter<TValueObject, TValue> : JsonConverter<TValueObject> + where TValueObject : PrimitiveValueObject<TValueObject, TValue> + where TValue : IComparable + { + /// <inheritdoc /> + public override bool CanWrite => true; + + /// <inheritdoc /> + public override bool CanRead => true; + + /// <inheritdoc /> + public override void WriteJson(JsonWriter writer, TValueObject value, JsonSerializer serializer) + { + if(value is null) + { + writer.WriteNull(); + } + else + { + writer.WriteValue(value.Value); + } + } + + /// <inheritdoc /> + public override TValueObject ReadJson(JsonReader reader, Type objectType, TValueObject existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if(reader.TokenType == JsonToken.Null) + { + return null; + } + + TValue value = serializer.Deserialize<TValue>(reader); + object instance = Activator.CreateInstance(objectType, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TValueObject)instance; + } + } +} diff --git a/src/Fluxera.ValueObject.LiteDB/BsonMapperExtensions.cs b/src/Fluxera.ValueObject.LiteDB/BsonMapperExtensions.cs new file mode 100644 index 0000000..5d8bf62 --- /dev/null +++ b/src/Fluxera.ValueObject.LiteDB/BsonMapperExtensions.cs @@ -0,0 +1,40 @@ +namespace Fluxera.ValueObject.LiteDB +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Fluxera.Guards; + using global::LiteDB; + using JetBrains.Annotations; + + /// <summary> + /// Extension methods for the <see cref="BsonMapper" /> type. + /// </summary> + [PublicAPI] + public static class BsonMapperExtensions + { + /// <summary> + /// Configure the mapper to use the <see cref="PrimitiveValueObjectConverter" />. + /// </summary> + /// <param name="mapper"></param> + /// <returns></returns> + public static BsonMapper UsePrimitiveValueObject(this BsonMapper mapper) + { + Guard.Against.Null(mapper); + + IEnumerable<Type> primitiveValueObjectTypes = AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => x.IsPrimitiveValueObject()); + + foreach(Type primitiveValueObjectType in primitiveValueObjectTypes) + { + mapper.RegisterType(primitiveValueObjectType, + PrimitiveValueObjectConverter.Serialize(primitiveValueObjectType), + PrimitiveValueObjectConverter.Deserialize(primitiveValueObjectType)); + } + + return mapper; + } + } +} diff --git a/src/Fluxera.ValueObject.LiteDB/Fluxera.ValueObject.LiteDB.csproj b/src/Fluxera.ValueObject.LiteDB/Fluxera.ValueObject.LiteDB.csproj new file mode 100644 index 0000000..763ad08 --- /dev/null +++ b/src/Fluxera.ValueObject.LiteDB/Fluxera.ValueObject.LiteDB.csproj @@ -0,0 +1,37 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup> + <Title>Fluxera.ValueObject.LiteDB</Title> + <Description>A libary that provides serializer support for LiteDB for value objects.</Description> + <PackageTags>fluxera;library;ddd;value-object;json;litedb</PackageTags> + </PropertyGroup> + + <ItemGroup> + <None Include="..\..\README.md" Link="Properties\README.md"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + <None Include="..\..\icon.png" Link="Properties\icon.png"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="GitVersion.MsBuild" Version="5.10.1"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" /> + <PackageReference Include="LiteDB" Version="5.0.11" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Fluxera.ValueObject\Fluxera.ValueObject.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Fluxera.ValueObject.LiteDB/PrimitiveValueObjectConverter.cs b/src/Fluxera.ValueObject.LiteDB/PrimitiveValueObjectConverter.cs new file mode 100644 index 0000000..9421e37 --- /dev/null +++ b/src/Fluxera.ValueObject.LiteDB/PrimitiveValueObjectConverter.cs @@ -0,0 +1,51 @@ +namespace Fluxera.ValueObject.LiteDB +{ + using System; + using System.Reflection; + using global::LiteDB; + using JetBrains.Annotations; + + /// <summary> + /// A converter for primitive value objects. + /// </summary> + [PublicAPI] + public static class PrimitiveValueObjectConverter + { + /// <summary> + /// Serialize the given primitive value object instance. + /// </summary> + /// <param name="primitiveValueObjectType"></param> + /// <returns></returns> + public static Func<object, BsonValue> Serialize(Type primitiveValueObjectType) + { + return obj => + { + PropertyInfo property = primitiveValueObjectType.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance); + object value = property?.GetValue(obj); + + BsonValue bsonValue = new BsonValue(value); + return bsonValue; + }; + } + + /// <summary> + /// Deserialize a primitive value object instance from the given bson value. + /// </summary> + /// <param name="primitiveValueObjectType"></param> + /// <returns></returns> + public static Func<BsonValue, object> Deserialize(Type primitiveValueObjectType) + { + return bson => + { + if(bson.IsNull) + { + return null; + } + + object value = bson.RawValue; + object instance = Activator.CreateInstance(primitiveValueObjectType, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return instance; + }; + } + } +} diff --git a/src/Fluxera.ValueObject.MongoDB/ConventionPackExtensions.cs b/src/Fluxera.ValueObject.MongoDB/ConventionPackExtensions.cs new file mode 100644 index 0000000..77257bc --- /dev/null +++ b/src/Fluxera.ValueObject.MongoDB/ConventionPackExtensions.cs @@ -0,0 +1,24 @@ +namespace Fluxera.ValueObject.MongoDB +{ + using global::MongoDB.Bson.Serialization.Conventions; + using JetBrains.Annotations; + + /// <summary> + /// Extension methods for the <see cref="ConventionPack" /> type. + /// </summary> + [PublicAPI] + public static class ConventionPackExtensions + { + /// <summary> + /// Configure the serializer to use the <see cref="PrimitiveValueObjectSerializer{TValueObject,TValue}" />. + /// </summary> + /// <param name="pack"></param> + /// <returns></returns> + public static ConventionPack UsePrimitiveValueObject(this ConventionPack pack) + { + pack.Add(new PrimitiveValueObjectConvention()); + + return pack; + } + } +} diff --git a/src/Fluxera.ValueObject.MongoDB/Fluxera.ValueObject.MongoDB.csproj b/src/Fluxera.ValueObject.MongoDB/Fluxera.ValueObject.MongoDB.csproj new file mode 100644 index 0000000..c4111a4 --- /dev/null +++ b/src/Fluxera.ValueObject.MongoDB/Fluxera.ValueObject.MongoDB.csproj @@ -0,0 +1,37 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup> + <Title>Fluxera.ValueObject.MongoDB</Title> + <Description>A libary that provides serializer support for MongoDB for value objects.</Description> + <PackageTags>fluxera;library;ddd;value-object;json;mongodb</PackageTags> + </PropertyGroup> + + <ItemGroup> + <None Include="..\..\README.md" Link="Properties\README.md"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + <None Include="..\..\icon.png" Link="Properties\icon.png"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="GitVersion.MsBuild" Version="5.10.1"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" /> + <PackageReference Include="MongoDB.Driver" Version="2.15.1" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Fluxera.ValueObject\Fluxera.ValueObject.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Fluxera.ValueObject.MongoDB/PrimitiveValueObjectConvention.cs b/src/Fluxera.ValueObject.MongoDB/PrimitiveValueObjectConvention.cs new file mode 100644 index 0000000..3b1afc9 --- /dev/null +++ b/src/Fluxera.ValueObject.MongoDB/PrimitiveValueObjectConvention.cs @@ -0,0 +1,31 @@ +namespace Fluxera.ValueObject.MongoDB +{ + using System; + using global::MongoDB.Bson.Serialization; + using global::MongoDB.Bson.Serialization.Conventions; + using JetBrains.Annotations; + + /// <summary> + /// A convention that enables support for serializing primitive value object properties. + /// </summary> + [PublicAPI] + public sealed class PrimitiveValueObjectConvention : ConventionBase, IMemberMapConvention + { + /// <inheritdoc /> + public void Apply(BsonMemberMap memberMap) + { + Type originalMemberType = memberMap.MemberType; + Type memberType = Nullable.GetUnderlyingType(originalMemberType) ?? originalMemberType; + + if(memberType.IsPrimitiveValueObject()) + { + Type valueType = memberType.GetValueType(); + Type serializerTypeTemplate = typeof(PrimitiveValueObjectSerializer<,>); + Type serializerType = serializerTypeTemplate.MakeGenericType(memberType, valueType); + + IBsonSerializer enumerationSerializer = (IBsonSerializer)Activator.CreateInstance(serializerType); + memberMap.SetSerializer(enumerationSerializer); + } + } + } +} diff --git a/src/Fluxera.ValueObject.MongoDB/PrimitiveValueObjectSerializer.cs b/src/Fluxera.ValueObject.MongoDB/PrimitiveValueObjectSerializer.cs new file mode 100644 index 0000000..5c714ae --- /dev/null +++ b/src/Fluxera.ValueObject.MongoDB/PrimitiveValueObjectSerializer.cs @@ -0,0 +1,47 @@ +namespace Fluxera.ValueObject.MongoDB +{ + using System; + using System.Reflection; + using global::MongoDB.Bson; + using global::MongoDB.Bson.Serialization; + using global::MongoDB.Bson.Serialization.Serializers; + using JetBrains.Annotations; + + /// <summary> + /// A serializer that handles instances of primitive value objects. + /// </summary> + /// <typeparam name="TValueObject"></typeparam> + /// <typeparam name="TValue"></typeparam> + [PublicAPI] + public sealed class PrimitiveValueObjectSerializer<TValueObject, TValue> : SerializerBase<TValueObject> + where TValueObject : PrimitiveValueObject<TValueObject, TValue> + where TValue : IComparable + { + /// <inheritdoc /> + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValueObject value) + { + if(value is null) + { + context.Writer.WriteNull(); + } + else + { + BsonSerializer.Serialize(context.Writer, value.Value); + } + } + + /// <inheritdoc /> + public override TValueObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + if(context.Reader.CurrentBsonType == BsonType.Null) + { + context.Reader.ReadNull(); + return null; + } + + TValue value = BsonSerializer.Deserialize<TValue>(context.Reader); + object instance = Activator.CreateInstance(args.NominalType, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TValueObject)instance; + } + } +} diff --git a/src/Fluxera.ValueObject.SystemTextJson/EnumerationJsonConverterFactory.cs b/src/Fluxera.ValueObject.SystemTextJson/EnumerationJsonConverterFactory.cs new file mode 100644 index 0000000..6ba7eef --- /dev/null +++ b/src/Fluxera.ValueObject.SystemTextJson/EnumerationJsonConverterFactory.cs @@ -0,0 +1,29 @@ +namespace Fluxera.ValueObject.SystemTextJson +{ + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + using JetBrains.Annotations; + + /// <inheritdoc /> + [PublicAPI] + public sealed class PrimitiveValueObjectJsonConverterFactory : JsonConverterFactory + { + /// <inheritdoc /> + public override bool CanConvert(Type typeToConvert) + { + bool isPrimitiveValueObject = typeToConvert.IsPrimitiveValueObject(); + return isPrimitiveValueObject; + } + + /// <inheritdoc /> + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + Type valueType = typeToConvert.GetValueType(); + Type converterTypeTemplate = typeof(PrimitiveValueObjectConverter<,>); + Type converterType = converterTypeTemplate.MakeGenericType(typeToConvert, valueType); + + return (JsonConverter)Activator.CreateInstance(converterType); + } + } +} diff --git a/src/Fluxera.ValueObject.SystemTextJson/Fluxera.ValueObject.SystemTextJson.csproj b/src/Fluxera.ValueObject.SystemTextJson/Fluxera.ValueObject.SystemTextJson.csproj new file mode 100644 index 0000000..be2ee65 --- /dev/null +++ b/src/Fluxera.ValueObject.SystemTextJson/Fluxera.ValueObject.SystemTextJson.csproj @@ -0,0 +1,37 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup> + <Title>Fluxera.ValueObject.SystemTextJson</Title> + <Description>A libary that provides serializer support for System.Text.Json for value objects.</Description> + <PackageTags>fluxera;library;ddd;value-object;json</PackageTags> + </PropertyGroup> + + <ItemGroup> + <None Include="..\..\README.md" Link="Properties\README.md"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + <None Include="..\..\icon.png" Link="Properties\icon.png"> + <Pack>true</Pack> + <PackagePath>\</PackagePath> + </None> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="GitVersion.MsBuild" Version="5.10.1"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="JetBrains.Annotations" Version="2022.1.0" /> + <PackageReference Include="System.Text.Json" Version="6.0.4" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Fluxera.ValueObject\Fluxera.ValueObject.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Fluxera.ValueObject.SystemTextJson/JsonSerializerOptionsExtensions.cs b/src/Fluxera.ValueObject.SystemTextJson/JsonSerializerOptionsExtensions.cs new file mode 100644 index 0000000..5e53040 --- /dev/null +++ b/src/Fluxera.ValueObject.SystemTextJson/JsonSerializerOptionsExtensions.cs @@ -0,0 +1,21 @@ +namespace Fluxera.ValueObject.SystemTextJson +{ + using System.Text.Json; + using JetBrains.Annotations; + + /// <summary> + /// Extension methods for the <see cref="JsonSerializerOptions" /> type. + /// </summary> + [PublicAPI] + public static class JsonSerializerOptionsExtensions + { + /// <summary> + /// Configures the serializer to use the <see cref="PrimitiveValueObjectConverter{TValueObject,TValue}" />. + /// </summary> + /// <param name="options"></param> + public static void UsePrimitiveValueObject(this JsonSerializerOptions options) + { + options.Converters.Add(new PrimitiveValueObjectJsonConverterFactory()); + } + } +} diff --git a/src/Fluxera.ValueObject.SystemTextJson/PrimitiveValueObjectConverter.cs b/src/Fluxera.ValueObject.SystemTextJson/PrimitiveValueObjectConverter.cs new file mode 100644 index 0000000..4f2a754 --- /dev/null +++ b/src/Fluxera.ValueObject.SystemTextJson/PrimitiveValueObjectConverter.cs @@ -0,0 +1,41 @@ +namespace Fluxera.ValueObject.SystemTextJson +{ + using System; + using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization; + using JetBrains.Annotations; + + /// <inheritdoc /> + [PublicAPI] + public sealed class PrimitiveValueObjectConverter<TValueObject, TValue> : JsonConverter<TValueObject> + where TValueObject : PrimitiveValueObject<TValueObject, TValue> + where TValue : IComparable + { + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, TValueObject value, JsonSerializerOptions options) + { + if(value is null) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, value.Value, options); + } + } + + /// <inheritdoc /> + public override TValueObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if(reader.TokenType == JsonTokenType.Null) + { + return null; + } + + TValue value = JsonSerializer.Deserialize<TValue>(ref reader, options); + object instance = Activator.CreateInstance(typeToConvert, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TValueObject)instance; + } + } +} diff --git a/src/Fluxera.ValueObject/Fluxera.ValueObject.csproj b/src/Fluxera.ValueObject/Fluxera.ValueObject.csproj index 8798019..ddb5cb4 100644 --- a/src/Fluxera.ValueObject/Fluxera.ValueObject.csproj +++ b/src/Fluxera.ValueObject/Fluxera.ValueObject.csproj @@ -6,7 +6,7 @@ <PropertyGroup> <Title>Fluxera.ValueObject</Title> - <Description>An extendable guard implementation.</Description> + <Description>A value object implementation.</Description> <PackageTags>fluxera;library;ddd;value-object</PackageTags> </PropertyGroup> diff --git a/src/Fluxera.ValueObject/PrimitiveValueObject.cs b/src/Fluxera.ValueObject/PrimitiveValueObject.cs index fa0e29c..219f544 100644 --- a/src/Fluxera.ValueObject/PrimitiveValueObject.cs +++ b/src/Fluxera.ValueObject/PrimitiveValueObject.cs @@ -44,7 +44,6 @@ protected PrimitiveValueObject(TValue value) /// </summary> public TValue Value { get; private set; } - /// <inheritdoc /> public int CompareTo(PrimitiveValueObject<TValueObject, TValue> other) { diff --git a/src/Fluxera.ValueObject/PrimitiveValueObjectExtensions.cs b/src/Fluxera.ValueObject/PrimitiveValueObjectExtensions.cs new file mode 100644 index 0000000..d431242 --- /dev/null +++ b/src/Fluxera.ValueObject/PrimitiveValueObjectExtensions.cs @@ -0,0 +1,60 @@ +namespace Fluxera.ValueObject +{ + using System; + using JetBrains.Annotations; + + /// <summary> + /// Extension methods for the <see cref="Type" /> type. + /// </summary> + [PublicAPI] + public static class PrimitiveValueObjectExtensions + { + /// <summary> + /// Checks the given type if it is an <see cref=" PrimitiveValueObject{TValueObejct, TValue}" />. + /// </summary> + /// <param name="type"></param> + /// <returns>True, if the type is an enumeration, false otherwise.</returns> + public static bool IsPrimitiveValueObject(this Type type) + { + if(type is null || type.IsAbstract || type.IsGenericTypeDefinition) + { + return false; + } + + do + { + if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(PrimitiveValueObject<,>)) + { + return true; + } + + type = type.BaseType; + } + while(type is not null); + + return false; + } + + /// <summary> + /// Gets the type of the generic value parameter from the base type. + /// </summary> + /// <param name="type"></param> + /// <returns>The type of the value.</returns> + public static Type GetValueType(this Type type) + { + do + { + if(type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(PrimitiveValueObject<,>)) + { + Type valueType = type.GetGenericArguments()[1]; + return valueType; + } + + type = type?.BaseType; + } + while(type is not null); + + return null!; + } + } +} diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/DbContextFactory.cs b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/DbContextFactory.cs new file mode 100644 index 0000000..21d66f3 --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/DbContextFactory.cs @@ -0,0 +1,21 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore.UnitTests +{ + using System.Linq; + + public static class DbContextFactory + { + public static TestDbContext Generate(int seedCount) + { + PersonFactory.Initialize(); + + TestDbContext context = new TestDbContext + { + SeedData = PersonFactory.Generate(seedCount).ToArray(), + }; + + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + return context; + } + } +} diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests.csproj b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests.csproj new file mode 100644 index 0000000..4fd7164 --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests.csproj @@ -0,0 +1,24 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Bogus" Version="34.0.2" /> + <PackageReference Include="FluentAssertions" Version="6.7.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.5" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="NUnit" Version="3.13.3" /> + <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> + <PackageReference Include="NUnit.Analyzers" Version="3.3.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Fluxera.ValueObject.EntityFrameworkCore\Fluxera.ValueObject.EntityFrameworkCore.csproj" /> + </ItemGroup> + +</Project> diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/ModelBuilderExtensionsTests.cs b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/ModelBuilderExtensionsTests.cs new file mode 100644 index 0000000..f438131 --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/ModelBuilderExtensionsTests.cs @@ -0,0 +1,21 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore.UnitTests +{ + using System.Collections.Generic; + using System.Linq; + using FluentAssertions; + using NUnit.Framework; + + [TestFixture] + public class ModelBuilderExtensionsTests + { + [Test] + public void ShouldUseNameConverter() + { + int seedCount = 1; + using TestDbContext context = DbContextFactory.Generate(seedCount); + List<Person> people = context.Set<Person>().ToList(); + + people.Should().BeEquivalentTo(context.SeedData); + } + } +} diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/NoModelCacheKeyFactory.cs b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/NoModelCacheKeyFactory.cs new file mode 100644 index 0000000..fdb92e2 --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/NoModelCacheKeyFactory.cs @@ -0,0 +1,17 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore.UnitTests +{ + using System; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + + /// <summary> + /// Creates a unique cache key every time to prevent caching. + /// </summary> + public class NoModelCacheKeyFactory : IModelCacheKeyFactory + { + public object Create(DbContext context) + { + return Guid.NewGuid(); + } + } +} diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/Person.cs b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/Person.cs new file mode 100644 index 0000000..284195d --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/Person.cs @@ -0,0 +1,12 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore.UnitTests +{ + using System.ComponentModel.DataAnnotations; + + public class Person + { + [Key] + public string Id { get; set; } + + public StringPrimitive StringPrimitive { get; set; } + } +} diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/PersonFactory.cs b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/PersonFactory.cs new file mode 100644 index 0000000..a5c53cc --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/PersonFactory.cs @@ -0,0 +1,22 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore.UnitTests +{ + using System; + using System.Collections.Generic; + using Bogus; + + public static class PersonFactory + { + public static IList<Person> Generate(int count) + { + return new Faker<Person>() + .RuleFor(e => e.Id, (f, e) => f.Random.Guid().ToString()) + .RuleFor(e => e.StringPrimitive, (f, e) => new StringPrimitive("12345")) + .Generate(count); + } + + public static void Initialize() + { + Randomizer.Seed = new Random(62392); + } + } +} diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/StringPrimitive.cs b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/StringPrimitive.cs new file mode 100644 index 0000000..3fa47b8 --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/StringPrimitive.cs @@ -0,0 +1,10 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore.UnitTests +{ + public sealed class StringPrimitive : PrimitiveValueObject<StringPrimitive, string> + { + /// <inheritdoc /> + public StringPrimitive(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/TestDbContext.cs b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/TestDbContext.cs new file mode 100644 index 0000000..8cecbce --- /dev/null +++ b/tests/Fluxera.ValueObject.EntityFrameworkCore.UnitTests/TestDbContext.cs @@ -0,0 +1,67 @@ +namespace Fluxera.ValueObject.EntityFrameworkCore.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + using Microsoft.Extensions.Logging; + + public class TestDbContext : DbContext + { + public bool IsLoggingSensitiveData { get; set; } + + public ILoggerFactory LoggerFactory { get; set; } + + public DbSet<Person> People { get; set; } + + public IEnumerable<object> SeedData { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if(optionsBuilder == null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + optionsBuilder.UseInMemoryDatabase("TestDatabase"); + + if(this.SeedData != null) + { + optionsBuilder.ReplaceService<IModelCacheKeyFactory, NoModelCacheKeyFactory>(); + } + + optionsBuilder.UseLoggerFactory(this.LoggerFactory); + if(this.IsLoggingSensitiveData) + { + optionsBuilder.EnableSensitiveDataLogging(); + } + + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + if(modelBuilder == null) + { + throw new ArgumentNullException(nameof(modelBuilder)); + } + + if(this.SeedData != null) + { + IEnumerable<Type> types = this.SeedData.Select(x => x.GetType()).Distinct(); + foreach(Type type in types) + { + EntityTypeBuilder entityBuilder = modelBuilder.Entity(type); + object[] data = this.SeedData.Where(x => x.GetType() == type).ToArray(); + entityBuilder.HasData(data); + } + } + + modelBuilder.UsePrimitiveValueObject(); + + base.OnModelCreating(modelBuilder); + } + } +} diff --git a/tests/Fluxera.ValueObject.JsonNet.UnitTests/ConverterTests.cs b/tests/Fluxera.ValueObject.JsonNet.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..92d55f3 --- /dev/null +++ b/tests/Fluxera.ValueObject.JsonNet.UnitTests/ConverterTests.cs @@ -0,0 +1,52 @@ +namespace Fluxera.ValueObject.JsonNet.UnitTests +{ + using FluentAssertions; + using Newtonsoft.Json; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + [SetUp] + public void SetUp() + { + JsonConvert.DefaultSettings = () => + { + JsonSerializerSettings settings = new JsonSerializerSettings + { + Formatting = Formatting.None + }; + settings.UsePrimitiveValueObject(); + return settings; + }; + } + + public class TestClass + { + public StringPrimitive StringPrimitive { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + StringPrimitive = new StringPrimitive("12345") + }; + + private static readonly string JsonString = @"{""StringPrimitive"":""12345""}"; + + [Test] + public void ShouldDeserialize() + { + TestClass obj = JsonConvert.DeserializeObject<TestClass>(JsonString); + + obj.StringPrimitive.Should().Be(new StringPrimitive("12345")); + } + + [Test] + public void ShouldSerialize() + { + string json = JsonConvert.SerializeObject(TestInstance, Formatting.None); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.ValueObject.JsonNet.UnitTests/Fluxera.ValueObject.JsonNet.UnitTests.csproj b/tests/Fluxera.ValueObject.JsonNet.UnitTests/Fluxera.ValueObject.JsonNet.UnitTests.csproj new file mode 100644 index 0000000..8b7adae --- /dev/null +++ b/tests/Fluxera.ValueObject.JsonNet.UnitTests/Fluxera.ValueObject.JsonNet.UnitTests.csproj @@ -0,0 +1,21 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="FluentAssertions" Version="6.7.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> + <PackageReference Include="NUnit" Version="3.13.3" /> + <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> + <PackageReference Include="NUnit.Analyzers" Version="3.3.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Fluxera.ValueObject.JsonNet\Fluxera.ValueObject.JsonNet.csproj" /> + </ItemGroup> + +</Project> \ No newline at end of file diff --git a/tests/Fluxera.ValueObject.JsonNet.UnitTests/StringPrimitive.cs b/tests/Fluxera.ValueObject.JsonNet.UnitTests/StringPrimitive.cs new file mode 100644 index 0000000..4206168 --- /dev/null +++ b/tests/Fluxera.ValueObject.JsonNet.UnitTests/StringPrimitive.cs @@ -0,0 +1,10 @@ +namespace Fluxera.ValueObject.JsonNet.UnitTests +{ + public sealed class StringPrimitive : PrimitiveValueObject<StringPrimitive, string> + { + /// <inheritdoc /> + public StringPrimitive(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.ValueObject.LiteDB.UnitTests/ConverterTests.cs b/tests/Fluxera.ValueObject.LiteDB.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..7fed99b --- /dev/null +++ b/tests/Fluxera.ValueObject.LiteDB.UnitTests/ConverterTests.cs @@ -0,0 +1,45 @@ +namespace Fluxera.ValueObject.LiteDB.UnitTests +{ + using FluentAssertions; + using global::LiteDB; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + static ConverterTests() + { + BsonMapper.Global.UsePrimitiveValueObject(); + } + + public class TestClass + { + public StringPrimitive StringPrimitive { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + StringPrimitive = new StringPrimitive("12345") + }; + + private static readonly string JsonString = @"{""StringPrimitive"":""12345""}"; + + [Test] + public void ShouldDeserialize() + { + BsonDocument doc = (BsonDocument)JsonSerializer.Deserialize(JsonString); + TestClass obj = BsonMapper.Global.ToObject<TestClass>(doc); + + obj.StringPrimitive.Should().Be(new StringPrimitive("12345")); + } + + [Test] + public void ShouldSerialize() + { + BsonDocument doc = BsonMapper.Global.ToDocument(TestInstance); + string json = JsonSerializer.Serialize(doc); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.ValueObject.LiteDB.UnitTests/Fluxera.ValueObject.LiteDB.UnitTests.csproj b/tests/Fluxera.ValueObject.LiteDB.UnitTests/Fluxera.ValueObject.LiteDB.UnitTests.csproj new file mode 100644 index 0000000..e4b0692 --- /dev/null +++ b/tests/Fluxera.ValueObject.LiteDB.UnitTests/Fluxera.ValueObject.LiteDB.UnitTests.csproj @@ -0,0 +1,22 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="FluentAssertions" Version="6.7.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> + <PackageReference Include="NUnit" Version="3.13.3" /> + <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> + <PackageReference Include="NUnit.Analyzers" Version="3.3.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Fluxera.ValueObject.LiteDB\Fluxera.ValueObject.LiteDB.csproj" /> + </ItemGroup> + +</Project> diff --git a/tests/Fluxera.ValueObject.LiteDB.UnitTests/StringPrimitive.cs b/tests/Fluxera.ValueObject.LiteDB.UnitTests/StringPrimitive.cs new file mode 100644 index 0000000..276b628 --- /dev/null +++ b/tests/Fluxera.ValueObject.LiteDB.UnitTests/StringPrimitive.cs @@ -0,0 +1,10 @@ +namespace Fluxera.ValueObject.LiteDB.UnitTests +{ + public sealed class StringPrimitive : PrimitiveValueObject<StringPrimitive, string> + { + /// <inheritdoc /> + public StringPrimitive(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.ValueObject.MongoDB.UnitTests/ConverterTests.cs b/tests/Fluxera.ValueObject.MongoDB.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..5f82c36 --- /dev/null +++ b/tests/Fluxera.ValueObject.MongoDB.UnitTests/ConverterTests.cs @@ -0,0 +1,47 @@ +namespace Fluxera.ValueObject.MongoDB.UnitTests +{ + using FluentAssertions; + using global::MongoDB.Bson; + using global::MongoDB.Bson.Serialization; + using global::MongoDB.Bson.Serialization.Conventions; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + static ConverterTests() + { + ConventionPack pack = new ConventionPack(); + pack.UsePrimitiveValueObject(); + ConventionRegistry.Register("ConventionPack", pack, t => true); + } + + public class TestClass + { + public StringPrimitive StringPrimitive { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + StringPrimitive = new StringPrimitive("12345") + }; + + private static readonly string JsonString = @"{ ""StringPrimitive"" : ""12345"" }"; + + [Test] + public void ShouldDeserialize() + { + TestClass obj = BsonSerializer.Deserialize<TestClass>(JsonString); + + obj.StringPrimitive.Should().Be(new StringPrimitive("12345")); + } + + [Test] + public void ShouldSerialize() + { + string json = TestInstance.ToJson(); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.ValueObject.MongoDB.UnitTests/Fluxera.ValueObject.MongoDB.UnitTests.csproj b/tests/Fluxera.ValueObject.MongoDB.UnitTests/Fluxera.ValueObject.MongoDB.UnitTests.csproj new file mode 100644 index 0000000..e29d7a2 --- /dev/null +++ b/tests/Fluxera.ValueObject.MongoDB.UnitTests/Fluxera.ValueObject.MongoDB.UnitTests.csproj @@ -0,0 +1,22 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="FluentAssertions" Version="6.7.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> + <PackageReference Include="NUnit" Version="3.13.3" /> + <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> + <PackageReference Include="NUnit.Analyzers" Version="3.3.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Fluxera.ValueObject.MongoDB\Fluxera.ValueObject.MongoDB.csproj" /> + </ItemGroup> + +</Project> diff --git a/tests/Fluxera.ValueObject.MongoDB.UnitTests/StringPrimitive.cs b/tests/Fluxera.ValueObject.MongoDB.UnitTests/StringPrimitive.cs new file mode 100644 index 0000000..e0c8a40 --- /dev/null +++ b/tests/Fluxera.ValueObject.MongoDB.UnitTests/StringPrimitive.cs @@ -0,0 +1,10 @@ +namespace Fluxera.ValueObject.MongoDB.UnitTests +{ + public sealed class StringPrimitive : PrimitiveValueObject<StringPrimitive, string> + { + /// <inheritdoc /> + public StringPrimitive(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/ConverterTests.cs b/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..f88ac93 --- /dev/null +++ b/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/ConverterTests.cs @@ -0,0 +1,46 @@ +namespace Fluxera.ValueObject.SystemTextJson.UnitTests +{ + using System.Text.Json; + using FluentAssertions; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + private static readonly JsonSerializerOptions options; + + static ConverterTests() + { + options = new JsonSerializerOptions(); + options.UsePrimitiveValueObject(); + } + + public class TestClass + { + public StringPrimitive StringPrimitive { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + StringPrimitive = new StringPrimitive("12345") + }; + + private static readonly string JsonString = @"{""StringPrimitive"":""12345""}"; + + [Test] + public void ShouldDeserialize() + { + TestClass obj = JsonSerializer.Deserialize<TestClass>(JsonString, options); + + obj.StringPrimitive.Should().Be(new StringPrimitive("12345")); + } + + [Test] + public void ShouldSerialize() + { + string json = JsonSerializer.Serialize(TestInstance, options); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/Fluxera.ValueObject.SystemTextJson.UnitTests.csproj b/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/Fluxera.ValueObject.SystemTextJson.UnitTests.csproj new file mode 100644 index 0000000..e9f467f --- /dev/null +++ b/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/Fluxera.ValueObject.SystemTextJson.UnitTests.csproj @@ -0,0 +1,22 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="FluentAssertions" Version="6.7.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> + <PackageReference Include="NUnit" Version="3.13.3" /> + <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> + <PackageReference Include="NUnit.Analyzers" Version="3.3.0" /> + <PackageReference Include="coverlet.collector" Version="3.1.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Fluxera.ValueObject.SystemTextJson\Fluxera.ValueObject.SystemTextJson.csproj" /> + </ItemGroup> + +</Project> diff --git a/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/StringPrimitive.cs b/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/StringPrimitive.cs new file mode 100644 index 0000000..33c5a8e --- /dev/null +++ b/tests/Fluxera.ValueObject.SystemTextJson.UnitTests/StringPrimitive.cs @@ -0,0 +1,10 @@ +namespace Fluxera.ValueObject.SystemTextJson.UnitTests +{ + public sealed class StringPrimitive : PrimitiveValueObject<StringPrimitive, string> + { + /// <inheritdoc /> + public StringPrimitive(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.ValueObject.UnitTests/Fluxera.ValueObject.UnitTests.csproj b/tests/Fluxera.ValueObject.UnitTests/Fluxera.ValueObject.UnitTests.csproj index fc06c0d..d4af245 100644 --- a/tests/Fluxera.ValueObject.UnitTests/Fluxera.ValueObject.UnitTests.csproj +++ b/tests/Fluxera.ValueObject.UnitTests/Fluxera.ValueObject.UnitTests.csproj @@ -6,14 +6,10 @@ <IsPackable>false</IsPackable> </PropertyGroup> - <ItemGroup> - <None Remove="ValueDictionaryTests.cs~RF146941a6.TMP" /> - </ItemGroup> - - <ItemGroup> - <PackageReference Include="FluentAssertions" Version="6.6.0" /> + <ItemGroup> + <PackageReference Include="FluentAssertions" Version="6.7.0" /> <PackageReference Include="Fluxera.Guards" Version="6.0.22" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> <PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageReference Include="coverlet.collector" Version="3.1.2">