aspnetcore: A published blazor wasm app throws a serialization error "Unhandled exception rendering component: ConstructorContainsNullParameterNames"

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

An error is thrown when making a JS interop call in a published blazor wasm app that should serialize a KeyValuePair value.

This seems to be related to trimming as running this with things like dotnet run --configuration Release works as expected, but publishing the app starts the trimmer by default.

In addition, using an anonymous type instead of KeyValuePair works without any errors.

[!NOTE] I tried to disable the trimming, but the error is still present. Any suggestions on how to do that will be helpful - this might turn out to be another issue though.

blazor-issue-wasm-serialization

Expected Behavior

No error is thrown.

Steps To Reproduce

  1. Clone https://github.com/Stamo-Gochev/blazor-issue-wasm-serialization
  2. Change directory to https://github.com/Stamo-Gochev/blazor-issue-wasm-serialization/tree/master/BlazorissueWasmSerialization/BlazorissueWasmSerialization
  3. Run dotnet publish --configuration Release
  4. Go to BlazorissueWasmSerialization/BlazorissueWasmSerialization/bin/Release/net8.0/publish
  5. Run dotnet BlazorissueWasmSerialization.dll
  6. Click the “Show issue button”

Exceptions (if any)

Exception
blazor.web.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String] SerializationNotSupportedParentType, System.Object Path: $.
System.NotSupportedException: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String] SerializationNotSupportedParentType, System.Object Path: $.
 ---> System.NotSupportedException: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String]
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException_ConstructorContainsNullParameterNames(Type )
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.PopulateParameterInfoValues(JsonTypeInfo )
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreateTypeInfoCore(Type , JsonConverter , JsonSerializerOptions )
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreateJsonTypeInfo(Type , JsonSerializerOptions )
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetTypeInfo(Type , JsonSerializerOptions )
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type )
   at System.Text.Json.JsonSerializerOptions.CachingContext.CreateCacheEntry(Type type, CachingContext context)
--- End of stack trace from previous location ---
   at System.Text.Json.JsonSerializerOptions.CachingContext.CacheEntry.GetResult()
   at System.Text.Json.JsonSerializerOptions.CachingContext.GetOrAddTypeInfo(Type , Boolean )
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type , Boolean , Nullable`1 , Boolean , Boolean )
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0()
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured()
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type , Boolean , Nullable`1 , Boolean , Boolean )
   at System.Text.Json.WriteStackFrame.InitializePolymorphicReEntry(Type , JsonSerializerOptions )
   at System.Text.Json.Serialization.JsonConverter.ResolvePolymorphicConverter(Object , JsonTypeInfo , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(Utf8JsonWriter , Object& , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.Converters.DictionaryOfTKeyTValueConverter`3[[System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnWriteResume(Utf8JsonWriter , Dictionary`2 , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonDictionaryConverter`3[[System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryWrite(Utf8JsonWriter , Dictionary`2 , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(Utf8JsonWriter , Dictionary`2& , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWriteAsObject(Utf8JsonWriter , Object , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(Utf8JsonWriter , Object& , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.Converters.ArrayConverter`2[[System.Object[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnWriteResume(Utf8JsonWriter , Object[] , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonCollectionConverter`2[[System.Object[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryWrite(Utf8JsonWriter , Object[] , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Object[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(Utf8JsonWriter , Object[]& , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Object[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(Utf8JsonWriter , Object[]& , JsonSerializerOptions , WriteStack& )
   Exception_EndOfInnerExceptionStack
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(WriteStack& , NotSupportedException )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Object[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(Utf8JsonWriter , Object[]& , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1[[System.Object[], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Serialize(Utf8JsonWriter , Object[]& , Object )
   at System.Text.Json.JsonSerializer.WriteString[Object[]](Object[]& , JsonTypeInfo`1 )
   at System.Text.Json.JsonSerializer.Serialize[Object[]](Object[] , JsonSerializerOptions )
   at Microsoft.JSInterop.JSRuntime.InvokeAsync[IJSVoidResult](Int64 , String , CancellationToken , Object[] )
   at Microsoft.JSInterop.JSRuntime.<InvokeAsync>d__16`1[[Microsoft.JSInterop.Infrastructure.IJSVoidResult, Microsoft.JSInterop, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext()
   at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime , String , Object[] )
   at BlazorissueWasmSerialization.Client.Pages.Home.OnButtonShowIssueClick()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task , ComponentState )

.NET Version

8.0.100

Anything else?

No response

About this issue

  • Original URL
  • State: open
  • Created 8 months ago
  • Reactions: 9
  • Comments: 27 (14 by maintainers)

Most upvoted comments

This seems to be a regression in the linker in fact.

@javiercn

@Stamo-Gochev has this ever worked in a previous version of Blazor?

What I suspect is happening is that the KeyValuePair is being linked out. That’s because the constructor and other elements are not being preserved. It works with an anonymous type because that type is created within the app assembly and Blazor doesn’t serialize that.

If the members are used elsewhere in the app, the issue will go away, but seems that they are only being used during serialization. You can use https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options?pivots=dotnet-8-0#root-descriptors and https://github.com/dotnet/runtime/blob/main/docs/tools/illink/data-formats.md#descriptor-format to teach the linker not to strip away members from the type.

If you are still running into issues after taking those steps, let us know, it might be a different linker issue.

Yes, it works with previous versions - .NET 6.0 and .NET 7.0.

The Home.razor page and the JS file can be pasted in a standalone .NET 6.0 and .NET 7.0 projects for comparison.

Regarding the suggestions:

If the members are used elsewhere in the app, the issue will go away, but seems that they are only being used during serialization. You can use https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options?pivots=dotnet-8-0#root-descriptors and https://github.com/dotnet/runtime/blob/main/docs/tools/illink/data-formats.md#descriptor-format to teach the linker not to strip away members from the type.

this is possible, but the change in the behavior looks more or less like a regression to me - it is not clear why KeyValuePair was serializeable before and not now.

As .NET 8.0 is a major release, I can accept a public issue that states that KeyValuePair is no longer serializable when linking is enabled, but I couldn’t find such an issue, which is why it seems like a regression to me.

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

See our Issue Management Policies for more information.

I’m getting the identical exception, also in a published WASM app serializing a KeyValue pair. Error does not happen locally. In my case this is the Telerik blazor grid serializing or deserializing a KeyValue pair under the hood that is related to a default date filter in the grid. As a result this error appears on any grid that has a filterable date column.

has this ever worked in a previous version of Blazor?

Yes it has, I tested it on the same exact project just with different targets, on .NET 7 it works fine, but on .NET 8 an exception occurs ☹️

https://github.com/dotnet/aspnetcore/assets/47293197/278e079c-90b4-4550-b614-1513e8118e21

here’s the project repo: https://github.com/SaifAqqad/blazor-bug-test

Imo even if this is the intended behavior of the trimmer, there should be a built-in exclusion of certain types (serialization, etc) for wasm projects

@taylorchasewhite If the issue happens at the linking stage, there are two possibilities:

  • The type is not being preserved; this requires an individual case analysis to determine whether it’s by design or whether there is an issue with any of our annotations.
  • The type is being correctly told to be preserved, but despite that, there are some issues. In that case, it can be a bug in the linker, but with the info collected we can point that out to the linker folks and they can further investigate.

In most situations, the member is simply not being preserved, and that matters most when you are using types from the framework, since gestures that you might expect will result in them being preserved (like being passed to a JS interop call) won’t actually do so.

In those situations, the descriptor file is the way to go.

Correct. Another thing to investigate is why the razor file in the repro doesn’t emit trimmer warnings even though it is calling trim-unsafe APIs. We want to be emphatic with customers that they cannot reliably depend on reflection in trimmed applications and they should be moving to using the source generator instead – emitting the relevant warnings would help a lot in that regard.

Blazor predates many of the linking/trimming work (in fact, it spearheaded it) and does not support full trimming. For example, we don’t trim customers dlls to prevent issues like this.

While I believe this is a regression, I am going to suggest we patch it on our side, since I think it’ll be easier.

It’s straightforward to repro. Details below performed using SDK 8.0.100 on Windows 11:

mkdir NullParameterNames
cd NullParameterNames
dotnet new blazor --interactivity WebAssembly

Replace contents of Counter.razor in client with the following:

@page "/counter"
@rendermode @(new InteractiveWebAssemblyRenderMode(false))
@using System.Text.Json

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<dl>
    @foreach (var item in @items!)
    {
        <dt>@item.Key</dt>
        <dd>@item.Value</dd>
    }
</dl>

@code {
    private List<KeyValuePair<string, string>>? items;

    protected override void OnInitialized()
    {
        string data = "[ { \"key\" : \"key 1\", \"value\" : \"value 1\" }, { \"key\" : \"key 2\", \"value\" : \"value 2\" } ]";

        JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true };

        items = JsonSerializer.Deserialize<List<KeyValuePair<string, string>>>(data, options)!;
    }
}

If you run server and visit counter page it will work as expected:

cd NullParameterNames
dotnet run --configuration Release

Afterwards publish the app and run the exe:

dotnet publish --configuration Release -p:PublishTrimmed=false
cd bin\Release\net8.0\publish
NullParameterNames.exe

On visiting counter page you will get System.NotSupportedException as detailed by OP.

The reason this error shows up on serialization traces as well as deserialization is because of this breaking change.

Does this mean that system.text.json doesn’t support de/serializing KeyValuePair? Since this is what this issue is about.

No. It means that using reflection-based serialization in trimmed applications is not reliable and can result in multiple failures like the one reported here. This is a problem inherent to using dynamic member access via reflection in trimmed applications and there’s not much that can be done about it other than adding manual annotations to work around the issues that pop up. Our recommendation is to switch to the source generator if you must use a trimmed app, see this article for more details.

Duplicate of dotnet/runtime#74141, https://github.com/dotnet/runtime/issues/94806 and https://github.com/dotnet/runtime/issues/81709.

The reason this error shows up on serialization traces as well as deserialization is because of this breaking change. I would recommend either switching to the source generator or applying a DynamicDependency attribute on the affected types (see https://github.com/passwordless-lib/fido2-net-lib/commit/1fbfb25cb9f53a8795ab952698dcc6493df1add3 for an example).