From 59ac901cae5bff5c873b62ec1555a2900d25d93b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?=
Date: Thu, 27 Oct 2022 12:40:06 +0200
Subject: [PATCH 01/32] Prepared sample page for type ids
---
.../Common/DotVVM.Samples.Common.csproj | 1 +
src/Samples/Common/DotvvmStartup.cs | 1 +
.../CompositeControls/BasicSampleViewModel.cs | 1 +
.../CustomPrimitiveTypes/BasicViewModel.cs | 134 ++++++++++++++++++
.../CustomPrimitiveTypes/Basic.dothtml | 32 +++++
5 files changed, 169 insertions(+)
create mode 100644 src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
create mode 100644 src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
diff --git a/src/Samples/Common/DotVVM.Samples.Common.csproj b/src/Samples/Common/DotVVM.Samples.Common.csproj
index 22023293b8..00c932322f 100644
--- a/src/Samples/Common/DotVVM.Samples.Common.csproj
+++ b/src/Samples/Common/DotVVM.Samples.Common.csproj
@@ -94,6 +94,7 @@
+
diff --git a/src/Samples/Common/DotvvmStartup.cs b/src/Samples/Common/DotvvmStartup.cs
index c041796624..c38a4964ed 100644
--- a/src/Samples/Common/DotvvmStartup.cs
+++ b/src/Samples/Common/DotvvmStartup.cs
@@ -196,6 +196,7 @@ private static void AddRoutes(DotvvmConfiguration config)
config.RouteTable.Add("FeatureSamples_ParameterBinding_OptionalParameterBinding2", "FeatureSamples/ParameterBinding/OptionalParameterBinding2/{Id?}", "Views/FeatureSamples/ParameterBinding/OptionalParameterBinding.dothtml", new { Id = 300 });
config.RouteTable.Add("FeatureSamples_Validation_Localization", "FeatureSamples/Validation/Localization", "Views/FeatureSamples/Validation/Localization.dothtml", presenterFactory: LocalizablePresenter.BasedOnQuery("lang"));
config.RouteTable.Add("FeatureSamples_Localization_Globalize", "FeatureSamples/Localization/Globalize", "Views/FeatureSamples/Localization/Globalize.dothtml", presenterFactory: LocalizablePresenter.BasedOnQuery("lang"));
+ config.RouteTable.Add("FeatureSamples_CustomPrimitiveTypes_Basic", "FeatureSamples/CustomPrimitiveTypes/Basic/{Id?}", "Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml");
config.RouteTable.AutoDiscoverRoutes(new DefaultRouteStrategy(config));
diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CompositeControls/BasicSampleViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CompositeControls/BasicSampleViewModel.cs
index 7ba58f7dc9..ebff37cfe4 100644
--- a/src/Samples/Common/ViewModels/FeatureSamples/CompositeControls/BasicSampleViewModel.cs
+++ b/src/Samples/Common/ViewModels/FeatureSamples/CompositeControls/BasicSampleViewModel.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using DotVVM.Framework.ViewModel;
+using DotVVM.Samples.Common.ViewModels.FeatureSamples.CustomPrimitiveTypes;
namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.CompositeControls
{
diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
new file mode 100644
index 0000000000..b647cd7463
--- /dev/null
+++ b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DotVVM.Framework.ViewModel;
+using DotVVM.Samples.Common.ViewModels.FeatureSamples.CompositeControls;
+using Newtonsoft.Json;
+
+namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.CustomPrimitiveTypes
+{
+ public class BasicViewModel : DotVVM.Samples.BasicSamples.ViewModels.ComplexSamples.SPA.SiteViewModel
+ {
+
+ [FromRoute("id")]
+ public SampleId IdInRoute { get; set; }
+
+ [FromQuery("id")]
+ public SampleId? IdInQuery { get; set; }
+
+ [Required]
+ public SampleId SelectedItemId { get; set; }
+
+ [Required]
+ public SampleId? SelectedItemNullableId { get; set; }
+
+
+ // použít typové ID ve viewmodelu
+ // [Required] validace
+ // musí fungovat i když je nullable
+
+ // vybírání hodnot v comboboxu, checkboxu + opět s podporou nullable
+
+
+ // routing -
+
+ // [FromRoute] a [FromQuery]
+
+ // JS translations
+
+ public List Items { get; set; } = new List
+ {
+ new SampleItem() { Id = SampleId.CreateExisting(new Guid("96c37b99-5fd5-448c-8a64-977ae11b8b8b")), Text = "Item 1" },
+ new SampleItem() { Id = SampleId.CreateExisting(new Guid("c2654a1f-3781-49a8-911b-c7346db166e0")), Text = "Item 2" },
+ new SampleItem() { Id = SampleId.CreateExisting(new Guid("e467a201-9ab7-4cd5-adbf-66edd03f6ae1")), Text = "Item 3" },
+ };
+
+ }
+
+ public class SampleItem
+ {
+ public SampleId Id { get; set; }
+ public string Text { get; set; }
+ }
+
+
+ [JsonConverter(typeof(TypeIdJsonConverter))]
+ public record SampleId : TypeId
+ {
+ public SampleId(Guid idValue) : base(idValue)
+ {
+ }
+ }
+
+ public abstract record TypeId : ITypeId
+ where TId : TypeId
+ {
+ public Guid IdValue { get; }
+
+ protected TypeId(Guid idValue)
+ {
+ if (idValue == default) throw new ArgumentException(nameof(idValue));
+ IdValue = idValue;
+ }
+
+ public static TId CreateNew()
+ {
+ var guid = Guid.NewGuid();
+ return (TId)Activator.CreateInstance(typeof(TId), args: guid)!;
+ }
+
+ public static TId CreateExisting(Guid idValue)
+ {
+ if (idValue == default) throw new ArgumentException(nameof(idValue));
+ return (TId)Activator.CreateInstance(typeof(TId), args: idValue)!;
+ }
+
+ public override string ToString()
+ {
+ return $"{GetType()} {{{IdValue}}}";
+ }
+ }
+
+ public interface ITypeId
+ {
+ Guid IdValue { get; }
+ }
+
+ public class TypeIdJsonConverter : JsonConverter where TId : TypeId
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(TId);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.String)
+ {
+ var idText = reader.ReadAsString();
+ var idValue = Guid.Parse(idText);
+ return TypeId.CreateExisting(idValue);
+ }
+ else if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+ else
+ {
+ throw new JsonSerializationException($"Token {reader.TokenType} cannot be deserialized as TypeId!");
+ }
+
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue((value as ITypeId)?.IdValue);
+ }
+
+ }
+
+}
+
diff --git a/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml b/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
new file mode 100644
index 0000000000..29a8b76560
--- /dev/null
+++ b/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
@@ -0,0 +1,32 @@
+@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.CustomPrimitiveTypes.BasicViewModel, DotVVM.Samples.Common
+@masterPage Views/ComplexSamples/SPA/site.dotmaster
+
+
+
+
+ Selected item ID: {{value: SelectedItemId}}
+
+
+
+
+
+ Selected nullable item ID: {{value: SelectedItemNullableId}}
+
+
+
+
+
+ Route parameter: {{value: IdInRoute}}
+
+ Query parameter: {{value: IdInQuery}}
+
+
+
+
+
From 29cbda67e8e0f936695ae34a68d1fac7e831f5ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?=
Date: Thu, 27 Oct 2022 13:20:57 +0200
Subject: [PATCH 02/32] Proof of concept of type metadata for custom primitive
types
---
.../CustomPrimitiveTypeRegistration.cs | 18 ++++++++++++
.../Configuration/DotvvmConfiguration.cs | 8 ++++++
.../DotvvmRuntimeConfiguration.cs | 10 +++++++
.../Framework/Utils/ReflectionUtils.cs | 22 +++++++++------
.../ViewModelTypeMetadataSerializer.cs | 25 +++++++++--------
.../ApplicationInsights.Owin/Web.config | 4 +--
src/Samples/Common/DotvvmStartup.cs | 3 ++
.../CustomPrimitiveTypes/BasicViewModel.cs | 3 +-
.../CustomPrimitiveTypes/Basic.dothtml | 28 +++++++++++++++----
9 files changed, 92 insertions(+), 29 deletions(-)
create mode 100644 src/Framework/Framework/Configuration/CustomPrimitiveTypeRegistration.cs
diff --git a/src/Framework/Framework/Configuration/CustomPrimitiveTypeRegistration.cs b/src/Framework/Framework/Configuration/CustomPrimitiveTypeRegistration.cs
new file mode 100644
index 0000000000..d1ae9219c7
--- /dev/null
+++ b/src/Framework/Framework/Configuration/CustomPrimitiveTypeRegistration.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace DotVVM.Framework.Configuration
+{
+ public class CustomPrimitiveTypeRegistration
+ {
+ // TODO: think about better name
+ public Type Type { get; }
+
+ public Type ClientSidePrimitiveType { get; }
+
+ public CustomPrimitiveTypeRegistration(Type type, Type clientSidePrimitiveType)
+ {
+ Type = type;
+ ClientSidePrimitiveType = clientSidePrimitiveType;
+ }
+ }
+}
diff --git a/src/Framework/Framework/Configuration/DotvvmConfiguration.cs b/src/Framework/Framework/Configuration/DotvvmConfiguration.cs
index e777b96c61..31e3d2708d 100644
--- a/src/Framework/Framework/Configuration/DotvvmConfiguration.cs
+++ b/src/Framework/Framework/Configuration/DotvvmConfiguration.cs
@@ -38,6 +38,11 @@ public sealed class DotvvmConfiguration
private bool isFrozen;
public const string DotvvmControlTagPrefix = "dot";
+ ///
+ /// Fired when the configuration routine is completely initialized and frozen.
+ ///
+ public event Action ConfigurationReady;
+
///
/// Gets or sets the application physical path.
///
@@ -167,6 +172,8 @@ public void Freeze()
_routeConstraints.Freeze();
Styles.Freeze();
FreezableList.Freeze(ref _compiledViewsAssemblies);
+
+ ConfigurationReady?.Invoke();
}
[JsonIgnore]
@@ -228,6 +235,7 @@ internal DotvvmConfiguration(IServiceProvider services)
RouteTable = new DotvvmRouteTable(this);
_styles = new StyleRepository(this);
+ ConfigurationReady += () => ReflectionUtils.RegisterCustomPrimitiveTypes(Runtime.CustomPrimitiveTypes);
}
private static ServiceCollection CreateDefaultServiceCollection()
diff --git a/src/Framework/Framework/Configuration/DotvvmRuntimeConfiguration.cs b/src/Framework/Framework/Configuration/DotvvmRuntimeConfiguration.cs
index e024df0b94..027266861b 100644
--- a/src/Framework/Framework/Configuration/DotvvmRuntimeConfiguration.cs
+++ b/src/Framework/Framework/Configuration/DotvvmRuntimeConfiguration.cs
@@ -16,12 +16,21 @@ public class DotvvmRuntimeConfiguration
public IList GlobalFilters => _globalFilters;
private IList _globalFilters;
+ ///
+ /// Gets types that are treated as primitive by DotVVM components and serialization.
+ ///
+ [JsonIgnore()]
+ public IList CustomPrimitiveTypes => _customPrimitiveTypes;
+ private IList _customPrimitiveTypes;
+
+
///
/// Initializes a new instance of the class.
///
public DotvvmRuntimeConfiguration()
{
_globalFilters = new FreezableList();
+ _customPrimitiveTypes = new FreezableList();
}
private bool isFrozen = false;
@@ -35,6 +44,7 @@ public void Freeze()
{
this.isFrozen = true;
FreezableList.Freeze(ref _globalFilters);
+ FreezableList.Freeze(ref _customPrimitiveTypes);
}
}
}
diff --git a/src/Framework/Framework/Utils/ReflectionUtils.cs b/src/Framework/Framework/Utils/ReflectionUtils.cs
index dd942ca4c3..2cf2d09d90 100644
--- a/src/Framework/Framework/Utils/ReflectionUtils.cs
+++ b/src/Framework/Framework/Utils/ReflectionUtils.cs
@@ -293,6 +293,8 @@ public record TypeConvertException(object Value, Type Type, Exception InnerExcep
typeof (double),
typeof (decimal)
};
+ // mapping of server-side types to their client-side representation
+ internal static readonly Dictionary CustomPrimitiveTypes = new Dictionary();
public static IEnumerable GetNumericTypes()
{
@@ -348,21 +350,14 @@ public static bool IsCollection(Type type)
return type != typeof(string) && IsEnumerable(type) && !IsDictionary(type);
}
- public static bool IsPrimitiveType(Type type)
+ public static bool IsPrimitiveType(this Type type)
{
return PrimitiveTypes.Contains(type)
+ || CustomPrimitiveTypes.ContainsKey(type)
|| (IsNullableType(type) && IsPrimitiveType(type.UnwrapNullableType()))
|| type.IsEnum;
}
- public static bool IsSerializationSupported(this Type type, bool includeNullables)
- {
- if (includeNullables)
- return IsPrimitiveType(type);
-
- return PrimitiveTypes.Contains(type);
- }
-
public static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
@@ -611,5 +606,14 @@ public static IEnumerable GetBaseTypesAndInterfaces(Type type)
type = baseType;
}
}
+
+ internal static void RegisterCustomPrimitiveTypes(IList customPrimitiveTypeRegistrations)
+ {
+ // TODO: validation
+ foreach (var registration in customPrimitiveTypeRegistrations)
+ {
+ CustomPrimitiveTypes.Add(registration.Type, registration.ClientSidePrimitiveType);
+ }
+ }
}
}
diff --git a/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs b/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs
index 3cc4fc524e..ab2b01b169 100644
--- a/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs
+++ b/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs
@@ -148,8 +148,21 @@ private ObjectMetadataWithDependencies BuildObjectTypeMetadata(ViewModelSerializ
internal JToken GetTypeIdentifier(Type type, HashSet dependentObjectTypes, HashSet dependentEnumTypes)
{
- if (type.IsSerializationSupported(includeNullables: false))
+ if (type.IsEnum)
{
+ dependentEnumTypes.Add(type);
+ return GetEnumTypeName(type);
+ }
+ else if (ReflectionUtils.IsNullable(type))
+ {
+ return GetNullableTypeIdentifier(type, dependentObjectTypes, dependentEnumTypes);
+ }
+ else if (type.IsPrimitiveType()) // we intentionally detect this after handling enums and nullable types
+ {
+ if (ReflectionUtils.CustomPrimitiveTypes.TryGetValue(type, out var clientSideType))
+ {
+ return GetTypeIdentifier(clientSideType, dependentObjectTypes, dependentEnumTypes);
+ }
return GetPrimitiveTypeName(type);
}
else if (type == typeof(object))
@@ -162,15 +175,6 @@ internal JToken GetTypeIdentifier(Type type, HashSet dependentObjectTypes,
var keyValuePair = typeof(KeyValuePair<,>).MakeGenericType(attrs);
return new JArray(GetTypeIdentifier(keyValuePair, dependentObjectTypes, dependentEnumTypes));
}
- else if (type.IsEnum)
- {
- dependentEnumTypes.Add(type);
- return GetEnumTypeName(type);
- }
- else if (ReflectionUtils.IsNullable(type))
- {
- return GetNullableTypeIdentifier(type, dependentObjectTypes, dependentEnumTypes);
- }
else if (ReflectionUtils.IsCollection(type))
{
return new JArray(GetTypeIdentifier(ReflectionUtils.GetEnumerableType(type)!, dependentObjectTypes, dependentEnumTypes));
@@ -226,7 +230,6 @@ private JObject BuildEnumTypeMetadata(Type type)
private string GetPrimitiveTypeName(Type type) => type.Name.ToString();
-
readonly struct ObjectMetadataWithDependencies
{
public JObject Metadata { get; }
diff --git a/src/Samples/ApplicationInsights.Owin/Web.config b/src/Samples/ApplicationInsights.Owin/Web.config
index caad38616d..a7b2698ee1 100644
--- a/src/Samples/ApplicationInsights.Owin/Web.config
+++ b/src/Samples/ApplicationInsights.Owin/Web.config
@@ -59,8 +59,8 @@
-
-
+
+
diff --git a/src/Samples/Common/DotvvmStartup.cs b/src/Samples/Common/DotvvmStartup.cs
index c38a4964ed..89e5c3105f 100644
--- a/src/Samples/Common/DotvvmStartup.cs
+++ b/src/Samples/Common/DotvvmStartup.cs
@@ -31,6 +31,7 @@
using DotVVM.Samples.Common.ViewModels.FeatureSamples.BindingVariables;
using DotVVM.Samples.Common.Views.ControlSamples.TemplateHost;
using DotVVM.Framework.ResourceManagement;
+using DotVVM.Samples.Common.ViewModels.FeatureSamples.CustomPrimitiveTypes;
namespace DotVVM.Samples.BasicSamples
{
@@ -80,6 +81,8 @@ public void Configure(DotvvmConfiguration config, string applicationPath)
config.AssertConfigurationIsValid();
config.RouteTable.Add("Errors_Routing_NonExistingView", "Errors/Routing/NonExistingView", "Views/Errors/Routing/NonExistingView.dothml");
+
+ config.Runtime.CustomPrimitiveTypes.Add(new CustomPrimitiveTypeRegistration(typeof(SampleId), typeof(Guid?)));
}
private void LoadSampleConfiguration(DotvvmConfiguration config, string applicationPath)
diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
index b647cd7463..633469c68a 100644
--- a/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
+++ b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
@@ -5,12 +5,11 @@
using System.Text;
using System.Threading.Tasks;
using DotVVM.Framework.ViewModel;
-using DotVVM.Samples.Common.ViewModels.FeatureSamples.CompositeControls;
using Newtonsoft.Json;
namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.CustomPrimitiveTypes
{
- public class BasicViewModel : DotVVM.Samples.BasicSamples.ViewModels.ComplexSamples.SPA.SiteViewModel
+ public class BasicViewModel : DotvvmViewModelBase
{
[FromRoute("id")]
diff --git a/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml b/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
index 29a8b76560..c6ceee5e7c 100644
--- a/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
+++ b/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
@@ -1,8 +1,26 @@
@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.CustomPrimitiveTypes.BasicViewModel, DotVVM.Samples.Common
-@masterPage Views/ComplexSamples/SPA/site.dotmaster
-
+
+
+
+
+ Conditional css classes
+
+
+
Selected item ID: {{value: SelectedItemId}}
@@ -26,7 +44,7 @@
Query parameter: {{value: IdInQuery}}
-
-
-
+
+
+
From 304a4ecd38ef707deead869c562728ea4cfa1097 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?=
Date: Thu, 27 Oct 2022 13:43:54 +0200
Subject: [PATCH 03/32] Custom primitive types URL and route parameter support
---
.../Framework/Utils/ReflectionUtils.cs | 6 ++++
.../CustomPrimitiveTypes/BasicViewModel.cs | 35 ++++++++++++++++++-
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/src/Framework/Framework/Utils/ReflectionUtils.cs b/src/Framework/Framework/Utils/ReflectionUtils.cs
index 2cf2d09d90..c83d4ca0de 100644
--- a/src/Framework/Framework/Utils/ReflectionUtils.cs
+++ b/src/Framework/Framework/Utils/ReflectionUtils.cs
@@ -22,6 +22,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using RecordExceptions;
+using System.ComponentModel;
namespace DotVVM.Framework.Utils
{
@@ -228,6 +229,11 @@ public static bool IsAssignableToGenericType(this Type givenType, Type genericTy
return Convert.ChangeType(long.Parse(str2, numberStyle & NumberStyles.Integer, CultureInfo.InvariantCulture), type, CultureInfo.InvariantCulture);
}
+ if (TypeDescriptor.GetConverter(type) is { } converter && converter.CanConvertFrom(value.GetType()))
+ {
+ return converter.ConvertTo(value, type);
+ }
+
// convert
try
{
diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
index 633469c68a..62ec647aef 100644
--- a/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
+++ b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
+using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using DotVVM.Core.Storage;
using DotVVM.Framework.ViewModel;
using Newtonsoft.Json;
@@ -53,7 +56,7 @@ public class SampleItem
public string Text { get; set; }
}
-
+ [TypeConverter(typeof(TypeIdConverter))]
[JsonConverter(typeof(TypeIdJsonConverter))]
public record SampleId : TypeId
{
@@ -62,6 +65,36 @@ public SampleId(Guid idValue) : base(idValue)
}
}
+ public class TypeIdConverter : TypeConverter
+ where TId : TypeId
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string) || sourceType == typeof(Guid) || sourceType == typeof(Guid?);
+ }
+
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return destinationType == typeof(TId);
+ }
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (value is string stringValue)
+ {
+ return TypeId.CreateExisting(new Guid(stringValue));
+ }
+ else if (value is Guid guidValue)
+ {
+ return TypeId.CreateExisting(guidValue);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
public abstract record TypeId : ITypeId
where TId : TypeId
{
From 9c728cb0596e504fc13a5f9426481dd83c53edbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?=
Date: Thu, 27 Oct 2022 14:03:20 +0200
Subject: [PATCH 04/32] Custom primitive types - added sample for commands and
fixed JSON converter
---
.../CustomPrimitiveTypes/BasicViewModel.cs | 24 +++++++++++++++++--
.../CustomPrimitiveTypes/Basic.dothtml | 14 ++++++++++-
2 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
index 62ec647aef..4aec4ff4c5 100644
--- a/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
+++ b/src/Samples/Common/ViewModels/FeatureSamples/CustomPrimitiveTypes/BasicViewModel.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -48,6 +49,26 @@ public class BasicViewModel : DotvvmViewModelBase
new SampleItem() { Id = SampleId.CreateExisting(new Guid("e467a201-9ab7-4cd5-adbf-66edd03f6ae1")), Text = "Item 3" },
};
+ public SampleId StaticCommandResult { get; set; }
+
+ [AllowStaticCommand]
+ public SampleId StaticCommandWithSampleId(SampleId? current)
+ {
+ if (!Items.Any(i => i.Id == current))
+ {
+ throw new Exception("The 'current' parameter didn't deserialize correctly.");
+ }
+ return SampleId.CreateExisting(new Guid("54162c7e-cdcc-4585-aa92-2e78be3f0c75"));
+ }
+
+ public void CommandWithSampleId(SampleId current)
+ {
+ if (!Items.Any(i => i.Id == current))
+ {
+ throw new Exception("The 'current' parameter didn't deserialize correctly.");
+ }
+ }
+
}
public class SampleItem
@@ -140,7 +161,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
if (reader.TokenType == JsonToken.String)
{
- var idText = reader.ReadAsString();
+ var idText = (string)reader.Value;
var idValue = Guid.Parse(idText);
return TypeId.CreateExisting(idValue);
}
@@ -152,7 +173,6 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
throw new JsonSerializationException($"Token {reader.TokenType} cannot be deserialized as TypeId!");
}
-
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
diff --git a/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml b/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
index c6ceee5e7c..b334973b6e 100644
--- a/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
+++ b/src/Samples/Common/Views/FeatureSamples/CustomPrimitiveTypes/Basic.dothtml
@@ -28,6 +28,7 @@
ItemTextBinding="{value: Text}"
ItemValueBinding="{value: Id}"
SelectedValue="{value: SelectedItemId}" />
+
@@ -37,6 +38,7 @@
ItemTextBinding="{value: Text}"
ItemValueBinding="{value: Id}"
SelectedValue="{value: SelectedItemNullableId}" />
+
@@ -44,7 +46,17 @@
Query parameter: {{value: IdInQuery}}
-
+
+
+
+
+
+
+
+
+
+ Static command result: {{value: StaticCommandResult}}
+