Skip to content

Commit

Permalink
serializer: disable cloning IDotvvmViewModel unless it has JsonConstr…
Browse files Browse the repository at this point in the history
…uctor
  • Loading branch information
exyi committed Dec 6, 2022
1 parent e2314a4 commit 7967007
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/Framework/Framework/Utils/ReflectionUtils.cs
Expand Up @@ -569,5 +569,9 @@ internal static void ClearCaches(Type[] types)
cache_GetTypeHash.TryRemove(t, out _);
}
}

internal static bool IsInitOnly(this PropertyInfo prop) =>
prop.SetMethod is { ReturnParameter: {} returnParameter } &&
returnParameter.GetRequiredCustomModifiers().Any(t => t == typeof(System.Runtime.CompilerServices.IsExternalInit));
}
}
Expand Up @@ -154,7 +154,18 @@ public ReaderDelegate CreateReaderFactory()
// If we have constructor property or if we have { get; init; } property, we always create new instance
var alwaysCallConstructor = Properties.Any(p => p.TransferToServer && (
p.ConstructorParameter is {} ||
true == p.PropertyInfo?.SetMethod?.ReturnParameter?.GetRequiredCustomModifiers().Any(t => t == typeof(System.Runtime.CompilerServices.IsExternalInit))));
p.PropertyInfo.IsInitOnly()));

// We don't want to clone IDotvvmViewModel automatically, because the user is likely to register this specific instance somewhere
if (alwaysCallConstructor && typeof(IDotvvmViewModel).IsAssignableFrom(Type) && !Constructor.IsDefined(typeof(JsonConstructorAttribute)))
{
var cloneReason =
Properties.FirstOrDefault(p => p.TransferToServer && p.PropertyInfo.IsInitOnly()) is {} initProperty
? $"init-only property {initProperty.Name} is transferred client → server" :
Properties.FirstOrDefault(p => p.TransferToServer && p.ConstructorParameter is {}) is {} ctorProperty
? $"property {ctorProperty.Name} must be injected into constructor parameter {ctorProperty.ConstructorParameter!.Name}" : "internal bug";
throw new Exception($"Deserialization of {Type.ToCode()} is not allowed, because it implements IDotvvmViewModel and {cloneReason}. To allow cloning the object on deserialization, mark a constructor with [JsonConstructor].");
}
var constructorCall = CallConstructor(servicesParameter, propertyVars, throwImmediately: alwaysCallConstructor);

// curly brackets are used for variables and methods from the context of this factory method
Expand Down

0 comments on commit 7967007

Please sign in to comment.