Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't deserialize object despite custom JsonSerializerContext in Blazor WASM #81709

Closed
1 task done
Regenhardt opened this issue Feb 6, 2023 · 10 comments
Closed
1 task done

Comments

@Regenhardt
Copy link
Contributor

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Deserializing an object with a [JsonConstructor] that has parameters in Blazor WASM throws an exception with ConstructorContainsNullParameterNames when published. It works in debug mode, hinting at that information being trimmed away somewhere, even though the containing type is included in a custom JsonSerializerContext and therefore serialization-logic is being generated.

One of the types in question, although only the first few lines are the important ones:

public sealed class PubKeyCredParam
{
    /// <summary>
    /// Constructs a PubKeyCredParam instance
    /// </summary>
    [JsonConstructor]
    public PubKeyCredParam(COSE.Algorithm alg, PublicKeyCredentialType type = PublicKeyCredentialType.PublicKey)
    {
        Type = type;
        Alg = alg;
    }

    /// <summary>
    /// The type member specifies the type of credential to be created.
    /// </summary>
    [JsonPropertyName("type")]
    public PublicKeyCredentialType Type { get; }

    /// <summary>
    /// The alg member specifies the cryptographic signature algorithm with which the newly generated credential will be used, and thus also the type of asymmetric key pair to be generated, e.g., RSA or Elliptic Curve.
    /// </summary>
    [JsonPropertyName("alg")]
    public COSE.Algorithm Alg { get; }

    public static readonly PubKeyCredParam ES256   = new(COSE.Algorithm.ES256); // External authenticators support the ES256 algorithm
    public static readonly PubKeyCredParam ES384   = new(COSE.Algorithm.ES384);
    public static readonly PubKeyCredParam ES512   = new(COSE.Algorithm.ES512);
    public static readonly PubKeyCredParam RS256   = new(COSE.Algorithm.RS256); // Supported by windows hello
    public static readonly PubKeyCredParam RS384   = new(COSE.Algorithm.RS384);
    public static readonly PubKeyCredParam RS512   = new(COSE.Algorithm.RS512);
    public static readonly PubKeyCredParam PS256   = new(COSE.Algorithm.PS256);
    public static readonly PubKeyCredParam PS384   = new(COSE.Algorithm.PS384);
    public static readonly PubKeyCredParam PS512   = new(COSE.Algorithm.PS512);
    public static readonly PubKeyCredParam Ed25519 = new(COSE.Algorithm.EdDSA);
}

Adding a [DynamicDependency] on itself to the constructor fixes this: passwordless-lib/fido2-net-lib@1fbfb25

The exception I paste below is for PublicKeyCredentialRpEntity, but since that is a bigger class I pasted this smaller one. Same problem though, as you can see in the linked commit.

Expected Behavior

The whole object should be deserialized, the generated deserializer using the marked [JsonConstructor].

Steps To Reproduce

Create two classes in a project shared by ASP.NET Core backend & Blazor WASM, A having a property of B (although this probably also happens when B is sent directly)
Give B a ctor with parameters but no default ctor.
Add custom JsonSerializerContext with [JsonSerializable] generating a serializer for the type you send.
Publish the project (letting the linker do some trimming).
Send object from backend to frontend, try to deserialize in Blazor WASM.

Exceptions (if any)

System.NotSupportedException: ConstructorContainsNullParameterNames, Fido2NetLib.PublicKeyCredentialRpEntity Path: $.rp | LineNumber: 0 | BytePositionInLine: 7. ---> System.NotSupportedException: ConstructorContainsNullParameterNames, Fido2NetLib.PublicKeyCredentialRpEntity at System.Text.Json.ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(Type ) at System.Text.Json.Serialization.Metadata.ReflectionJsonTypeInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].GetParameterInfoValues() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureLocked|143_0() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured() at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.get_JsonTypeInfo() at System.Text.Json.ReadStack.Push() at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , PublicKeyCredentialRpEntity& ) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadJsonAndSetMember(Object , ReadStack& , Utf8JsonReader& ) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].OnTryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& , JsonSerializerOptions , ReadStack& ) Exception_EndOfInnerExceptionStack at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& , Utf8JsonReader& , NotSupportedException ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& , JsonSerializerOptions , ReadStack& ) at System.Text.Json.JsonSerializer.ReadFromSpan[CredentialCreateOptions](ReadOnlySpan`1 , JsonTypeInfo , Nullable`1 ) at System.Text.Json.JsonSerializer.ReadFromSpan[CredentialCreateOptions](ReadOnlySpan`1 , JsonTypeInfo ) at System.Text.Json.JsonSerializer.Deserialize[CredentialCreateOptions](String , JsonSerializerOptions ) at WebAuthnExample.Client.StjDeserializer.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, ResponseDeserializerInfo info) at RestEase.Implementation.Requester.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, IRequestInfo requestInfo) at RestEase.Implementation.Requester.<RequestAsync>d__41`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].MoveNext() at WebAuthnExample.Client.Pages.Index.CreateCreds() System.NotSupportedException: ConstructorContainsNullParameterNames, Fido2NetLib.PublicKeyCredentialRpEntity at System.Text.Json.ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(Type ) at System.Text.Json.Serialization.Metadata.ReflectionJsonTypeInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].GetParameterInfoValues() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureLocked|143_0() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured() at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.get_JsonTypeInfo() at System.Text.Json.ReadStack.Push() at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , PublicKeyCredentialRpEntity& ) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadJsonAndSetMember(Object , ReadStack& , Utf8JsonReader& ) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].OnTryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& , JsonSerializerOptions , ReadStack& )

.NET Version

7.0.102

Anything else?

dotnet --info
.NET SDK:
Version: 7.0.102
Commit: 4bbdd14480

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19044
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.102\

Host:
Version: 7.0.2
Architecture: x64
Commit: d037e07

.NET SDKs installed:
3.1.426 [C:\Program Files\dotnet\sdk]
6.0.308 [C:\Program Files\dotnet\sdk]
7.0.102 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

@mkArtakMSFT mkArtakMSFT transferred this issue from dotnet/aspnetcore Feb 6, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Feb 6, 2023
@ghost
Copy link

ghost commented Feb 6, 2023

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Deserializing an object with a [JsonConstructor] that has parameters in Blazor WASM throws an exception with ConstructorContainsNullParameterNames when published. It works in debug mode, hinting at that information being trimmed away somewhere, even though the containing type is included in a custom JsonSerializerContext and therefore serialization-logic is being generated.

One of the types in question, although only the first few lines are the important ones:

public sealed class PubKeyCredParam
{
    /// <summary>
    /// Constructs a PubKeyCredParam instance
    /// </summary>
    [JsonConstructor]
    public PubKeyCredParam(COSE.Algorithm alg, PublicKeyCredentialType type = PublicKeyCredentialType.PublicKey)
    {
        Type = type;
        Alg = alg;
    }

    /// <summary>
    /// The type member specifies the type of credential to be created.
    /// </summary>
    [JsonPropertyName("type")]
    public PublicKeyCredentialType Type { get; }

    /// <summary>
    /// The alg member specifies the cryptographic signature algorithm with which the newly generated credential will be used, and thus also the type of asymmetric key pair to be generated, e.g., RSA or Elliptic Curve.
    /// </summary>
    [JsonPropertyName("alg")]
    public COSE.Algorithm Alg { get; }

    public static readonly PubKeyCredParam ES256   = new(COSE.Algorithm.ES256); // External authenticators support the ES256 algorithm
    public static readonly PubKeyCredParam ES384   = new(COSE.Algorithm.ES384);
    public static readonly PubKeyCredParam ES512   = new(COSE.Algorithm.ES512);
    public static readonly PubKeyCredParam RS256   = new(COSE.Algorithm.RS256); // Supported by windows hello
    public static readonly PubKeyCredParam RS384   = new(COSE.Algorithm.RS384);
    public static readonly PubKeyCredParam RS512   = new(COSE.Algorithm.RS512);
    public static readonly PubKeyCredParam PS256   = new(COSE.Algorithm.PS256);
    public static readonly PubKeyCredParam PS384   = new(COSE.Algorithm.PS384);
    public static readonly PubKeyCredParam PS512   = new(COSE.Algorithm.PS512);
    public static readonly PubKeyCredParam Ed25519 = new(COSE.Algorithm.EdDSA);
}

Adding a [DynamicDependency] on itself to the constructor fixes this: passwordless-lib/fido2-net-lib@1fbfb25

The exception I paste below is for PublicKeyCredentialRpEntity, but since that is a bigger class I pasted this smaller one. Same problem though, as you can see in the linked commit.

Expected Behavior

The whole object should be deserialized, the generated deserializer using the marked [JsonConstructor].

Steps To Reproduce

Create two classes in a project shared by ASP.NET Core backend & Blazor WASM, A having a property of B (although this probably also happens when B is sent directly)
Give B a ctor with parameters but no default ctor.
Add custom JsonSerializerContext with [JsonSerializable] generating a serializer for the type you send.
Publish the project (letting the linker do some trimming).
Send object from backend to frontend, try to deserialize in Blazor WASM.

Exceptions (if any)

System.NotSupportedException: ConstructorContainsNullParameterNames, Fido2NetLib.PublicKeyCredentialRpEntity Path: $.rp | LineNumber: 0 | BytePositionInLine: 7. ---> System.NotSupportedException: ConstructorContainsNullParameterNames, Fido2NetLib.PublicKeyCredentialRpEntity at System.Text.Json.ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(Type ) at System.Text.Json.Serialization.Metadata.ReflectionJsonTypeInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].GetParameterInfoValues() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureLocked|143_0() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured() at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.get_JsonTypeInfo() at System.Text.Json.ReadStack.Push() at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , PublicKeyCredentialRpEntity& ) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadJsonAndSetMember(Object , ReadStack& , Utf8JsonReader& ) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].OnTryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& , JsonSerializerOptions , ReadStack& ) Exception_EndOfInnerExceptionStack at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& , Utf8JsonReader& , NotSupportedException ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& , JsonSerializerOptions , ReadStack& ) at System.Text.Json.JsonSerializer.ReadFromSpan[CredentialCreateOptions](ReadOnlySpan`1 , JsonTypeInfo , Nullable`1 ) at System.Text.Json.JsonSerializer.ReadFromSpan[CredentialCreateOptions](ReadOnlySpan`1 , JsonTypeInfo ) at System.Text.Json.JsonSerializer.Deserialize[CredentialCreateOptions](String , JsonSerializerOptions ) at WebAuthnExample.Client.StjDeserializer.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, ResponseDeserializerInfo info) at RestEase.Implementation.Requester.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, IRequestInfo requestInfo) at RestEase.Implementation.Requester.<RequestAsync>d__41`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].MoveNext() at WebAuthnExample.Client.Pages.Index.CreateCreds() System.NotSupportedException: ConstructorContainsNullParameterNames, Fido2NetLib.PublicKeyCredentialRpEntity at System.Text.Json.ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(Type ) at System.Text.Json.Serialization.Metadata.ReflectionJsonTypeInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].GetParameterInfoValues() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureLocked|143_0() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured() at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.get_JsonTypeInfo() at System.Text.Json.ReadStack.Push() at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , PublicKeyCredentialRpEntity& ) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1[[Fido2NetLib.PublicKeyCredentialRpEntity, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadJsonAndSetMember(Object , ReadStack& , Utf8JsonReader& ) at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].OnTryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].TryRead(Utf8JsonReader& , Type , JsonSerializerOptions , ReadStack& , CredentialCreateOptions& ) at System.Text.Json.Serialization.JsonConverter`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].ReadCore(Utf8JsonReader& , JsonSerializerOptions , ReadStack& )

.NET Version

7.0.102

Anything else?

dotnet --info
.NET SDK:
Version: 7.0.102
Commit: 4bbdd14480

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19044
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.102\

Host:
Version: 7.0.2
Architecture: x64
Commit: d037e07

.NET SDKs installed:
3.1.426 [C:\Program Files\dotnet\sdk]
6.0.308 [C:\Program Files\dotnet\sdk]
7.0.102 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Author: Regenhardt
Assignees: -
Labels:

area-System.Text.Json

Milestone: -

@krwq
Copy link
Member

krwq commented Feb 15, 2023

I'm not yet sure if this is related to something we do in JSON or not - it might be by design. @eerhardt do you know perhaps?

@Regenhardt
Copy link
Contributor Author

it might be by design

Would be weird to have to give the constructor a dynamic dependency on itself by design. Or is there another way of doing that? Or does the custom JsonSerializerContext need more information to properly generate serialization logic?

@eerhardt
Copy link
Member

The issue here is that the trimmer is removing the parameter names for methods it doesn't think are reflected upon. It does this for all assemblies marked "IsTrimmable=true", which Fido2.Models is:

https://github.com/passwordless-lib/fido2-net-lib/blob/1fbfb25cb9f53a8795ab952698dcc6493df1add3/Src/Fido2.Models/Fido2.Models.csproj#L6

The reason the DynamicDependency works is because this tells the trimmer that the constructors of this type are being reflected upon, so it stops removing the names of the parameters on those constructors.

Add custom JsonSerializerContext with [JsonSerializable] generating a serializer for the type you send.

A big issue here is that you seem to be trying to use the source generator, but it is still using the Reflection based serializer (i.e. JsonSerializer is using Refleciton to walk over the constructors of the Type). You will get a more consistent behavior if you ensure the source generated code is used when you serialize the object between the server and client. (See the WebAuthnExample.Client.StjDeserializer.Deserialize which appears to be calling JsonSerializer.Deserialize.)

FYI - @agocke @vitek-karas @sbomer

@Regenhardt
Copy link
Contributor Author

It seems I did something wrong with deserialization then - when using that StjDeserializer with the custom serializer context (TypeInfoResolver = FidoModelSerializerContext.Default), I get ResolverTypeInfoOptionsNotCompatible when trying to deserialize a CredentialCreateOptions object, which is added to the custom context as [JsonSerializable(typeof(CredentialCreateOptions))].:

System.InvalidOperationException: ResolverTypeInfoOptionsNotCompatible
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ResolverTypeInfoOptionsNotCompatible()
at System.Text.Json.Serialization.JsonSerializerContext.System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo(Type , JsonSerializerOptions )
at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type )
at System.Collections.Concurrent.ConcurrentDictionary`2[[System.Type, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Text.Json.Serialization.Metadata.JsonTypeInfo, System.Text.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]].GetOrAdd(Type , Func`2 )
at System.Text.Json.JsonSerializerOptions.CachingContext.GetOrAddJsonTypeInfo(Type )
at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type , Boolean , Boolean )
at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions , Type )
at System.Text.Json.JsonSerializer.Deserialize[CredentialCreateOptions](String , JsonSerializerOptions )
at WebAuthnExample.Client.StjDeserializer.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, ResponseDeserializerInfo info)
at RestEase.Implementation.Requester.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, IRequestInfo requestInfo)
at RestEase.Implementation.Requester.d__41`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
at WebAuthnExample.Client.Pages.Index.CreateCreds()

I tried this so often, I even added a parameter to the call (https://blazor-webauthn.onrender.com/?customSerializer=true) so I could test it in a production-like environment.

When I "Go to implementation" on the serializer context, it does find a FidoModelSerializerContext.CredentialCreateOptions.cs, so I expected that one to work actually, thought maybe explicitly using was my mistake.

Do I need to add something else to my serializer to make it use the custom context?

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Feb 16, 2023

It seems I did something wrong with deserialization then - when using that StjDeserializer with the custom serializer context (TypeInfoResolver = FidoModelSerializerContext.Default), I get ResolverTypeInfoOptionsNotCompatible when trying to deserialize a CredentialCreateOptions object

Any chance you could be generating FidoModelSerializerContext using the .NET 6 sdk? Contexts generated using the v6 source generator are not compatible with contract customization and will produce the error you're seeing. You could work around this issue by creating a fresh context instance tied to the specific options:

var options = new JsonSerializerOptions();
_ = new FidoModelSerializerContext(options); // use a context that is specifically bound to the current instance

or just use the v7 sdk when generating the context.

@Regenhardt
Copy link
Contributor Author

The lib is build & published using v6, yes.

Not entirely sure what the sample code does - why put the options object into another options object and put that other object into the first object's TypeInfoResolver? I'm assuming there's supposed to be the custom serializer context there for now.

I passed a new FidoModelSerializerContext() as TypeInfoRsolver, which resulted in ResolverTypeInfoOptionsNotCompatible:

System.InvalidOperationException: ResolverTypeInfoOptionsNotCompatible
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ResolverTypeInfoOptionsNotCompatible()
at System.Text.Json.Serialization.JsonSerializerContext.System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo(Type , JsonSerializerOptions )
at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type )
at System.Collections.Concurrent.ConcurrentDictionary`2[[System.Type, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Text.Json.Serialization.Metadata.JsonTypeInfo, System.Text.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]].GetOrAdd(Type , Func`2 )
at System.Text.Json.JsonSerializerOptions.CachingContext.GetOrAddJsonTypeInfo(Type )
at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type , Boolean , Boolean )
at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions , Type )
at System.Text.Json.JsonSerializer.Deserialize[CredentialCreateOptions](String , JsonSerializerOptions )
at WebAuthnExample.Client.StjDeserializer.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, ResponseDeserializerInfo info)
at RestEase.Implementation.Requester.Deserialize[CredentialCreateOptions](String content, HttpResponseMessage response, IRequestInfo requestInfo)
at RestEase.Implementation.Requester.d__41`1[[Fido2NetLib.CredentialCreateOptions, Fido2.Models, Version=3.0.1.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
at WebAuthnExample.Client.Pages.Index.CreateCreds()

Then I created options and passed a nnew FidoModelSerializerContext(options) to its TypeInfoResolver, getting

System.InvalidOperationException: JsonSerializerOptions instances cannot be modified once encapsulated by a JsonSerializerContext. Such encapsulation can happen either when calling 'JsonSerializerOptions.AddContext' or when passing the options instance to a JsonSerializerContext constructor.

The other way around doesn't work, I can't add an options instance to a context after construction.

So after seeing it in the exception message, I tried options.AddContext<FidoModelSerializerContext>(); and this one worked!
Guess I'll make another custom context then for the models the frontend needs, thanks for the help!

@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Feb 16, 2023
@Regenhardt Regenhardt changed the title ConstructorContainsNullParameterNames despite custom JsonSerializerContext in Blazor WASM Can't deserialize object despite custom JsonSerializerContext in Blazor WASM Feb 16, 2023
@Regenhardt
Copy link
Contributor Author

(Edited the title to better describe the content for the next person having the problem I had)

@eiriktsarpalis
Copy link
Member

Not entirely sure what the sample code does - why put the options object into another options object and put that other object into the first object's TypeInfoResolver?

My apologies, my initial sample had an error. I've updated it for clarity.

I tried options.AddContext(); and this one worked!

Yes, you're absolutely right this should work best. I would strongly encourage you to regenerate the FidoModelSerializerContext using the latest version of the sdk though.

@Regenhardt
Copy link
Contributor Author

Since this is a library in the .NET Foundation, possibly (hopefully) used by many and big applications, would it make sense to stay with the LTS (.NET 6) and only update when a new LTS arrives (.NET 8)?
Gonna try and publish for multiple targets to offer both solutions for now, availability for older projects and newer features.

@ghost ghost locked as resolved and limited conversation to collaborators Mar 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants