diff --git a/src/Fluxera.ValueObject/PrimitiveValueObject.cs b/src/Fluxera.ValueObject/PrimitiveValueObject.cs
index db59def..fc271a3 100644
--- a/src/Fluxera.ValueObject/PrimitiveValueObject.cs
+++ b/src/Fluxera.ValueObject/PrimitiveValueObject.cs
@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
+ using System.ComponentModel;
using Fluxera.Guards;
using Fluxera.Utilities.Extensions;
using JetBrains.Annotations;
@@ -18,6 +19,7 @@
/// The type of the value object.
/// The type of the value.
[PublicAPI]
+ [TypeConverter(typeof(PrimitiveValueObjectConverter))]
public abstract class PrimitiveValueObject : ValueObject, IComparable
where TValueObject : PrimitiveValueObject
where TValue : IComparable
diff --git a/src/Fluxera.ValueObject/PrimitiveValueObjectConverter.cs b/src/Fluxera.ValueObject/PrimitiveValueObjectConverter.cs
new file mode 100644
index 0000000..0ff83bb
--- /dev/null
+++ b/src/Fluxera.ValueObject/PrimitiveValueObjectConverter.cs
@@ -0,0 +1,126 @@
+namespace Fluxera.ValueObject
+{
+ using System;
+ using System.Collections.Concurrent;
+ using System.ComponentModel;
+ using System.Globalization;
+ using Fluxera.Guards;
+
+ internal sealed class PrimitiveValueObjectConverter : TypeConverter
+ {
+ private static readonly ConcurrentDictionary ActualConverters = new ConcurrentDictionary();
+
+ private readonly TypeConverter innerConverter;
+
+ public PrimitiveValueObjectConverter(Type primitiveValueObjectType)
+ {
+ this.innerConverter = ActualConverters.GetOrAdd(primitiveValueObjectType, CreateActualConverter);
+ }
+
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return this.innerConverter.CanConvertFrom(context, sourceType);
+ }
+
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return this.innerConverter.CanConvertTo(context, destinationType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ return this.innerConverter.ConvertFrom(context, culture, value);
+ }
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ return this.innerConverter.ConvertTo(context, culture, value, destinationType);
+ }
+
+ private static TypeConverter CreateActualConverter(Type primitiveValueObjectType)
+ {
+ if(!primitiveValueObjectType.IsPrimitiveValueObject())
+ {
+ throw new InvalidOperationException($"The type '{primitiveValueObjectType}' is not a primitive value-object.");
+ }
+
+ Type valueType = primitiveValueObjectType.GetValueType();
+ Type actualConverterType = typeof(PrimitiveValueObjectConverter<,>).MakeGenericType(primitiveValueObjectType, valueType);
+ return (TypeConverter)Activator.CreateInstance(actualConverterType);
+ }
+ }
+
+ internal sealed class PrimitiveValueObjectConverter : TypeConverter
+ where TValueObject : PrimitiveValueObject
+ where TValue : IComparable
+ {
+ private static TypeConverter ValueConverter { get; } = GetIdValueConverter();
+
+ private static TypeConverter GetIdValueConverter()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(TValue));
+
+ if(!converter.CanConvertFrom(typeof(string)))
+ {
+ throw new InvalidOperationException(
+ $"The type '{typeof(TValue)}' doesn't have a converter that can convert from string.");
+ }
+
+ return converter;
+ }
+
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string)
+ || sourceType == typeof(TValue)
+ || base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return destinationType == typeof(string)
+ || destinationType == typeof(TValue)
+ || base.CanConvertTo(context, destinationType);
+ }
+
+ ///
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if(value is string str)
+ {
+ value = ValueConverter.ConvertFrom(str);
+ }
+
+ if(value is TValue idValue)
+ {
+ object instance = Activator.CreateInstance(typeof(TValueObject), new object[] { idValue });
+ return instance;
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ Guard.Against.Null(value);
+
+ PrimitiveValueObject primitiveValueObject = (PrimitiveValueObject)value;
+
+ TValue objectValue = primitiveValueObject.Value;
+ if(destinationType == typeof(string))
+ {
+ return objectValue.ToString();
+ }
+
+ if(destinationType == typeof(TValue))
+ {
+ return objectValue;
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+}
diff --git a/tests/Fluxera.ValueObject.UnitTests/TypeConverterTests.cs b/tests/Fluxera.ValueObject.UnitTests/TypeConverterTests.cs
new file mode 100644
index 0000000..1077933
--- /dev/null
+++ b/tests/Fluxera.ValueObject.UnitTests/TypeConverterTests.cs
@@ -0,0 +1,112 @@
+namespace Fluxera.ValueObject.UnitTests
+{
+ using System;
+ using System.ComponentModel;
+ using FluentAssertions;
+ using Fluxera.ValueObject.UnitTests.Model;
+ using NUnit.Framework;
+
+ [TestFixture]
+ public class TypeConverterTests
+ {
+ [Test]
+ public void ShouldConvertFromGuidStringValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));
+
+ GuidValue result = (GuidValue)converter.ConvertFromString("4c3668ce-3aaa-4adb-bece-05baa708e20f");
+ result.Should().NotBeNull();
+ result.Value.Should().NotBeEmpty().And.Be("4c3668ce-3aaa-4adb-bece-05baa708e20f");
+ }
+
+ [Test]
+ public void ShouldConvertFromGuidValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));
+
+ GuidValue result = (GuidValue)converter.ConvertFrom(Guid.Parse("4c3668ce-3aaa-4adb-bece-05baa708e20f"));
+ result.Should().NotBeNull();
+ result.Value.Should().NotBeEmpty().And.Be(Guid.Parse("4c3668ce-3aaa-4adb-bece-05baa708e20f"));
+ }
+
+ [Test]
+ public void ShouldConvertFromIntegerStringValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));
+
+ IntValue result = (IntValue)converter.ConvertFromString("999");
+ result.Should().NotBeNull();
+ result.Value.Should().Be(999);
+ }
+
+ [Test]
+ public void ShouldConvertFromIntegerValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));
+
+ IntValue result = (IntValue)converter.ConvertFrom(999);
+ result.Should().NotBeNull();
+ result.Value.Should().Be(999);
+ }
+
+ [Test]
+ public void ShouldConvertFromStringValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(StringValue));
+
+ StringValue result = (StringValue)converter.ConvertFromString("12345");
+ result.Should().NotBeNull();
+ result.Value.Should().NotBeEmpty().And.Be("12345");
+ }
+
+ [Test]
+ public void ShouldConvertToGuidStringValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));
+
+ GuidValue id = new GuidValue(Guid.Parse("2ca3459d-794e-4d25-9594-bc3849972e1f"));
+ string result = converter.ConvertToString(id);
+ result.Should().Be("2ca3459d-794e-4d25-9594-bc3849972e1f");
+ }
+
+ [Test]
+ public void ShouldConvertToGuidValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(GuidValue));
+
+ GuidValue id = new GuidValue(Guid.Parse("2ca3459d-794e-4d25-9594-bc3849972e1f"));
+ Guid result = (Guid)converter.ConvertTo(null, null, id, typeof(Guid));
+ result.Should().Be(Guid.Parse("2ca3459d-794e-4d25-9594-bc3849972e1f"));
+ }
+
+ [Test]
+ public void ShouldConvertToIntegerStringValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));
+
+ IntValue id = new IntValue(999);
+ string result = converter.ConvertToString(id);
+ result.Should().Be("999");
+ }
+
+ [Test]
+ public void ShouldConvertToIntegerValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(IntValue));
+
+ IntValue id = new IntValue(999);
+ int result = (int)converter.ConvertTo(null, null, id, typeof(int));
+ result.Should().Be(999);
+ }
+
+ [Test]
+ public void ShouldConvertToStringValue()
+ {
+ TypeConverter converter = TypeDescriptor.GetConverter(typeof(StringValue));
+
+ StringValue id = new StringValue("12345");
+ string result = converter.ConvertToString(id);
+ result.Should().Be("12345");
+ }
+ }
+}