| | | 1 | | using System.Diagnostics.CodeAnalysis; |
| | | 2 | | using System.Reflection; |
| | | 3 | | using System.Runtime.CompilerServices; |
| | | 4 | | using System.Text.Json.Serialization; |
| | | 5 | | using System.Text.Json.Serialization.Metadata; |
| | | 6 | | |
| | | 7 | | namespace Elsa.Workflows.Serialization.Configurators; |
| | | 8 | | |
| | | 9 | | /// <summary> |
| | | 10 | | /// Configures the contract resolver to add support for using non-default, private constructors for deserialization. |
| | | 11 | | /// </summary> |
| | | 12 | | public class CustomConstructorConfigurator : SerializationOptionsConfiguratorBase |
| | | 13 | | { |
| | | 14 | | /// <inheritdoc /> |
| | | 15 | | public override IEnumerable<Action<JsonTypeInfo>> GetModifiers() |
| | | 16 | | { |
| | | 17 | | // Set the default constructor for all types that have a default constructor. |
| | 771 | 18 | | yield return jsonTypeInfo => |
| | 771 | 19 | | { |
| | 158298 | 20 | | if (jsonTypeInfo is not { Kind: JsonTypeInfoKind.Object, CreateObject: null }) |
| | 72585 | 21 | | return; |
| | 771 | 22 | | |
| | 85713 | 23 | | var defaultConstructor = GetDefaultConstructor(jsonTypeInfo.Type); |
| | 85713 | 24 | | if (defaultConstructor != null) |
| | 771 | 25 | | { |
| | 4051 | 26 | | jsonTypeInfo.CreateObject = defaultConstructor; |
| | 771 | 27 | | } |
| | 86484 | 28 | | }; |
| | 771 | 29 | | } |
| | | 30 | | |
| | | 31 | | private static Func<object>? GetDefaultConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Public |
| | | 32 | | { |
| | 187661 | 33 | | foreach (var constructor in type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Ins |
| | | 34 | | { |
| | | 35 | | // If we have a default constructor, use that one. |
| | 10143 | 36 | | if (!constructor.GetParameters().Any()) |
| | 2565 | 37 | | return () => constructor.Invoke(default, [])!; |
| | | 38 | | |
| | | 39 | | // Else, find a constructor with the following signature: (string?, int?). |
| | | 40 | | // Check for a constructor with the following signature: |
| | | 41 | | // ctor(string, int) where string is decorated with [CallerFilePath] and int is decorated with [CallerLineNu |
| | 7578 | 42 | | var parameters = constructor.GetParameters(); |
| | | 43 | | |
| | | 44 | | // Check parameter count |
| | 7578 | 45 | | if (parameters.Length != 2) continue; |
| | | 46 | | |
| | | 47 | | // Does the constructor have a [JsonConstructor] attribute? |
| | 5882 | 48 | | var isJsonConstructor = constructor.GetCustomAttribute<JsonConstructorAttribute>() != null; |
| | | 49 | | |
| | | 50 | | // Check first parameter type and attribute |
| | 5882 | 51 | | if (parameters[0].ParameterType != typeof(string) || |
| | 5882 | 52 | | parameters[0].DefaultValue != null || |
| | 5882 | 53 | | (parameters[0].GetCustomAttribute<CallerFilePathAttribute>() == null && !isJsonConstructor)) continue; |
| | | 54 | | |
| | | 55 | | // Check second parameter type and attribute |
| | 1486 | 56 | | if (parameters[1].ParameterType != typeof(int?) || |
| | 1486 | 57 | | parameters[1].DefaultValue != null || |
| | 1486 | 58 | | (parameters[1].GetCustomAttribute<CallerLineNumberAttribute>() == null && !isJsonConstructor)) continue; |
| | | 59 | | |
| | 2507 | 60 | | return () => constructor.Invoke([null!, 0]); |
| | | 61 | | } |
| | | 62 | | |
| | 81662 | 63 | | return null; |
| | | 64 | | } |
| | | 65 | | } |