diff --git a/src/Framework/Framework/Runtime/HotReloadMetadataUpdateHandler.cs b/src/Framework/Framework/Runtime/HotReloadMetadataUpdateHandler.cs index 7dab915f55..30a46c3269 100644 --- a/src/Framework/Framework/Runtime/HotReloadMetadataUpdateHandler.cs +++ b/src/Framework/Framework/Runtime/HotReloadMetadataUpdateHandler.cs @@ -19,6 +19,7 @@ namespace DotVVM.Framework.Runtime public static class HotReloadMetadataUpdateHandler { internal static readonly ConcurrentBag> SerializationMappers = new(); + internal static readonly ConcurrentBag> TypeMetadataSerializer = new(); internal static readonly ConcurrentBag> UserColumnMappingCaches = new(); internal static readonly ConcurrentBag> ExtensionMethodsCaches = new(); public static void ClearCache(Type[]? updatedTypes) @@ -36,6 +37,11 @@ public static void ClearCache(Type[]? updatedTypes) problematicTypes.Add(u); } } + foreach (var tRef in TypeMetadataSerializer) + { + if (tRef.TryGetTarget(out var t)) + t.ClearCaches(updatedTypes); + } foreach (var cRef in UserColumnMappingCaches) { if (cRef.TryGetTarget(out var c)) @@ -47,7 +53,6 @@ public static void ClearCache(Type[]? updatedTypes) e.ClearCaches(updatedTypes); } - ViewModelTypeMetadataSerializer.ClearCaches(updatedTypes); DefaultViewModelLoader.ClearCaches(updatedTypes); AttributeViewModelParameterBinder.ClearCaches(updatedTypes); ChildViewModelsCache.ClearCaches(updatedTypes); diff --git a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index dddffd8e26..eb35dc99f4 100644 --- a/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/Framework/Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -133,12 +133,6 @@ public void BuildViewModel(IDotvvmRequestContext context, object? commandResult) if (viewModelConverter.EncryptedValues.Count > 0) viewModelToken["$encryptedValues"] = viewModelProtector.Protect(viewModelConverter.EncryptedValues.ToString(Formatting.None), context); - // serialize validation rules - bool useClientSideValidation = context.Configuration.ClientSideValidation; - var validationRules = useClientSideValidation ? - SerializeValidationRules(viewModelConverter) : - null; - // create result object var result = new JObject(); result["viewModel"] = viewModelToken; @@ -162,9 +156,6 @@ public void BuildViewModel(IDotvvmRequestContext context, object? commandResult) result["renderedResources"] = JArray.FromObject(context.ResourceManager.GetNamedResourcesInOrder().Select(r => r.Name)); } - // TODO: do not send on postbacks - if (validationRules?.Count > 0) result["validationRules"] = validationRules; - if (commandResult != null) result["commandResult"] = WriteCommandData(commandResult, serializer, "the command result"); AddCustomPropertiesIfAny(context, serializer, result); @@ -264,28 +255,6 @@ public JObject BuildResourcesJson(IDotvvmRequestContext context, Func - /// Serializes the validation rules. - /// - private JObject SerializeValidationRules(ViewModelJsonConverter viewModelConverter) - { - var validationRules = new JObject(); - foreach (var map in viewModelConverter.UsedSerializationMaps) - { - var rule = new JObject(); - - foreach (var property in map.Properties) - { - if (property.ValidationRules.Count > 0 && property.ClientValidationRules.Any()) - rule[property.Name] = JToken.FromObject(property.ClientValidationRules); - } - if (rule.Count > 0) validationRules[map.Type.GetTypeHash()] = rule; - } - return validationRules; - } - - - /// /// Serializes the redirect action. /// diff --git a/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs b/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs index 88efa3afcc..8849d27f08 100644 --- a/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs +++ b/src/Framework/Framework/ViewModel/Serialization/ViewModelTypeMetadataSerializer.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading; using DotVVM.Framework.Configuration; +using DotVVM.Framework.Runtime; using DotVVM.Framework.Utils; using FastExpressionCompiler; using Newtonsoft.Json.Linq; @@ -18,13 +19,17 @@ public class ViewModelTypeMetadataSerializer : IViewModelTypeMetadataSerializer { private readonly IViewModelSerializationMapper viewModelSerializationMapper; private readonly bool debug; - private static readonly ConcurrentDictionary cachedObjectMetadata = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary cachedEnumMetadata = new ConcurrentDictionary(); + private readonly bool serializeValidationRules; + private readonly ConcurrentDictionary cachedObjectMetadata = new ConcurrentDictionary(); + private readonly ConcurrentDictionary cachedEnumMetadata = new ConcurrentDictionary(); public ViewModelTypeMetadataSerializer(IViewModelSerializationMapper viewModelSerializationMapper, DotvvmConfiguration? config = null) { this.viewModelSerializationMapper = viewModelSerializationMapper; this.debug = config != null && config.Debug; + this.serializeValidationRules = config is null || config.ClientSideValidation; + + HotReloadMetadataUpdateHandler.TypeMetadataSerializer.Add(new(this)); } public JObject SerializeTypeMetadata(IEnumerable usedSerializationMaps, ISet? ignoredTypes = null) @@ -128,7 +133,7 @@ private ObjectMetadataWithDependencies BuildObjectTypeMetadata(ViewModelSerializ prop["update"] = "no"; } - if (property.ValidationRules.Any() && property.ClientValidationRules.Any()) + if (serializeValidationRules && property.ValidationRules.Any() && property.ClientValidationRules.Any()) { prop["validationRules"] = JToken.FromObject(property.ClientValidationRules); } @@ -275,7 +280,7 @@ public override int GetHashCode() } /// Clear caches for the specified types - internal static void ClearCaches(Type[] types) + internal void ClearCaches(Type[] types) { foreach (var t in types) cachedEnumMetadata.TryRemove(t, out _); diff --git a/src/Tests/ViewModel/ViewModelTypeMetadataSerializerTests.cs b/src/Tests/ViewModel/ViewModelTypeMetadataSerializerTests.cs index 0226ddfaf7..0edb24a9e4 100644 --- a/src/Tests/ViewModel/ViewModelTypeMetadataSerializerTests.cs +++ b/src/Tests/ViewModel/ViewModelTypeMetadataSerializerTests.cs @@ -1,15 +1,18 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Text; using CheckTestOutput; using DotVVM.Framework.Configuration; using DotVVM.Framework.ViewModel; +using DotVVM.Framework.Utils; using DotVVM.Framework.ViewModel.Serialization; using DotVVM.Framework.ViewModel.Validation; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using DotVVM.Framework.Testing; namespace DotVVM.Framework.Tests.ViewModel { @@ -74,6 +77,34 @@ public void ViewModelTypeMetadata_TypeMetadata() }); } + [TestMethod] + public void ViewModelTypeMetadata_ValidationRules() + { + CultureUtils.RunWithCulture("en-US", () => { + var typeMetadataSerializer = new ViewModelTypeMetadataSerializer(mapper); + var result = typeMetadataSerializer.SerializeTypeMetadata(new[] { mapper.GetMap(typeof(TestViewModel)) }); + + var rules = XAssert.IsType(result[typeof(TestViewModel).GetTypeHash()]["properties"]["ServerToClient"]["validationRules"]); + XAssert.Single(rules); + Assert.AreEqual("required", rules[0]["ruleName"].Value()); + Assert.AreEqual("ServerToClient is required!", rules[0]["errorMessage"].Value()); + }); + } + + [TestMethod] + public void ViewModelTypeMetadata_ValidationDisabled() + { + CultureUtils.RunWithCulture("en-US", () => { + var config = DotvvmTestHelper.CreateConfiguration(); + config.ClientSideValidation = false; + + var typeMetadataSerializer = new ViewModelTypeMetadataSerializer(mapper, config); + var result = typeMetadataSerializer.SerializeTypeMetadata(new[] { mapper.GetMap(typeof(TestViewModel)) }); + + XAssert.Null(result[typeof(TestViewModel).GetTypeHash()]["properties"]["ServerToClient"]["validationRules"]); + }); + } + [Flags] enum SampleEnum {