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

Error when serializing InteractiveRequestOptionsSerializerContext #52291

Closed
1 task done
thomasbach-dk opened this issue Nov 22, 2023 · 7 comments · Fixed by #54225
Closed
1 task done

Error when serializing InteractiveRequestOptionsSerializerContext #52291

thomasbach-dk opened this issue Nov 22, 2023 · 7 comments · Fixed by #54225
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug. feature-blazor-navigation Pillar: Technical Debt

Comments

@thomasbach-dk
Copy link

thomasbach-dk commented Nov 22, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

After upgrading to .NET 8 I get an error when calling NavigationManager.NavigateToLogin using the constructor with parameter type InteractiveRequestOptions as second parameter, where InteractiveRequestOptions include a key named extraQueryParams and with a Dictionary<string, string> as value (using TryAddAdditionalParameter within InteractiveRequestOptions).

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: JsonTypeInfo metadata for type 'System.Collections.Generic.Dictionary`2[System.String,System.String]' was not provided by TypeInfoResolver of type 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptionsSerializerContext'. If using source generation, ensure that all root types passed to the serializer have been annotated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically. The unsupported member type is located on type 'System.Object'. Path: $. System.NotSupportedException: JsonTypeInfo metadata for type 'System.Collections.Generic.Dictionary`2[System.String,System.String]' was not provided by TypeInfoResolver of type 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptionsSerializerContext'. If using source generation, ensure that all root types passed to the serializer have been annotated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically. The unsupported member type is located on type 'System.Object'. Path: $. ---> System.NotSupportedException: JsonTypeInfo metadata for type 'System.Collections.Generic.Dictionary`2[System.String,System.String]' was not provided by TypeInfoResolver of type 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptionsSerializerContext'. If using source generation, ensure that all root types passed to the serializer have been annotated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver resolver)
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType)
   at System.Text.Json.WriteStackFrame.InitializePolymorphicReEntry(Type runtimeType, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter.ResolvePolymorphicConverter(Object value, JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(Utf8JsonWriter writer, Object& value, JsonSerializerOptions options, WriteStack& state)
   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 writer, Dictionary`2 value, JsonSerializerOptions options, WriteStack& state)
   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 writer, Dictionary`2 dictionary, JsonSerializerOptions options, WriteStack& state)
   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 writer, Dictionary`2& value, JsonSerializerOptions options, WriteStack& state)
   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]].WriteCore(Utf8JsonWriter writer, Dictionary`2& value, JsonSerializerOptions options, WriteStack& state) 
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(WriteStack& state, NotSupportedException ex)
   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]].WriteCore(Utf8JsonWriter writer, Dictionary`2& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`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]].Serialize(Utf8JsonWriter writer, Dictionary`2& rootValue, Object rootValueBoxed)
   at System.Text.Json.JsonSerializer.Serialize[Dictionary`2](Utf8JsonWriter writer, Dictionary`2 value, JsonTypeInfo`1 jsonTypeInfo)
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptionsSerializerContext.OptionsRecordSerializeHandler(Utf8JsonWriter writer, OptionsRecord value)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptions.Converter.OptionsRecord, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].Serialize(Utf8JsonWriter writer, OptionsRecord& rootValue, Object rootValueBoxed)
   at System.Text.Json.JsonSerializer.Serialize[OptionsRecord](Utf8JsonWriter writer, OptionsRecord value, JsonTypeInfo`1 jsonTypeInfo)
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptions.Converter.Write(Utf8JsonWriter writer, InteractiveRequestOptions value, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptions, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].TryWrite(Utf8JsonWriter writer, InteractiveRequestOptions& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptions, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].WriteCore(Utf8JsonWriter writer, InteractiveRequestOptions& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptions, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].Serialize(Utf8JsonWriter writer, InteractiveRequestOptions& rootValue, Object rootValueBoxed)
   at System.Text.Json.JsonSerializer.WriteString[InteractiveRequestOptions](InteractiveRequestOptions& value, JsonTypeInfo`1 jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Serialize[InteractiveRequestOptions](InteractiveRequestOptions value, JsonTypeInfo`1 jsonTypeInfo)
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.InteractiveRequestOptions.ToState()
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.NavigationManagerExtensions.NavigateToLogin(NavigationManager manager, String loginPath, InteractiveRequestOptions request)
   at X.UI.WebApp.Pages.SignIn.SignIn.NavigateToLogin() in X:\x\source\WebApp\Pages\SignIn\SignIn.razor:line 40
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

.NET Version

8.0.100

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Nov 22, 2023
@mkArtakMSFT mkArtakMSFT added bug This issue describes a behavior which is not expected - a bug. NativeAOT Pillar: Technical Debt and removed NativeAOT labels Nov 22, 2023
@mkArtakMSFT mkArtakMSFT added this to the Planning: WebUI milestone Nov 22, 2023
@Dennizzz
Copy link

Dennizzz commented Dec 1, 2023

Could be related to #51870

@ghost
Copy link

ghost commented Dec 18, 2023

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@mkArtakMSFT mkArtakMSFT modified the milestones: .NET 9 Planning, 8.0.x Dec 18, 2023
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 6, 2024
@wtgodbe wtgodbe removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@dotnet dotnet deleted a comment from dotnet-policy-service bot Feb 13, 2024
@halter73 halter73 removed this from the 8.0.x milestone Feb 28, 2024
@halter73
Copy link
Member

halter73 commented Feb 28, 2024

After upgrading to .NET 8 I get an error when calling NavigationManager.NavigateToLogin using the constructor with parameter type InteractiveRequestOptions as second parameter, where InteractiveRequestOptions include a key named extraQueryParams and with a Dictionary<string, string> as value (using TryAddAdditionalParameter within InteractiveRequestOptions).

@thomasback-dk Can you show your NavigateToLogin call and how you constructed InteractiveRequestOptions? Is there a reason you couldn't add each key individually as a stringValue rather than adding a single extraQueryParams that's a Dictionary<string, string>?

If you cannot add each key individually, one workaround might be to serialize the Dictionary<string, string> to a JSON string manually using JsonSerializer.Serialize and deserialize manually using JsonSerializer.Deserialize after calling TryGetAdditionalParameter.

Changing your Dictionary<string, string> to Dictionary<string, object> might also work since that's already registered with the InteractiveRequestOptionsSerializerContext, but after deserializing all the object values in the nested dictionary value will probably be JsonElement's, so that might not be ideal.

The fact that TryAddAdditionalParameter<TValue>, and TryGetAdditionalParameter<TValue> don't work for all TValues and that there's no way to plug in your own custom types to the InteractiveRequestOptionsSerializerContext is indeed a problem, but I don't think it raises to the level of needing a fix patched into .NET 8 considering that there are workarounds and a proper fix will likely need new API to register custom types with the JsonSerializerContext assuming we don't just start using JsonOptions and do something similar to IdentityEndpointsJsonOptionsSetup to configure the needed types for source generation.

internal sealed class IdentityEndpointsJsonOptionsSetup : IConfigureOptions<JsonOptions>
{
public void Configure(JsonOptions options)
{
// Put our resolver in front of the reflection-based one. See ProblemDetailsOptionsSetup for a detailed explanation.
options.SerializerOptions.TypeInfoResolverChain.Insert(0, IdentityEndpointsJsonSerializerContext.Default);
}
}

@halter73 halter73 removed their assignment Feb 28, 2024
@thomasbach-dk
Copy link
Author

thomasbach-dk commented Feb 28, 2024

After upgrading to .NET 8 I get an error when calling NavigationManager.NavigateToLogin using the constructor with parameter type InteractiveRequestOptions as second parameter, where InteractiveRequestOptions include a key named extraQueryParams and with a Dictionary<string, string> as value (using TryAddAdditionalParameter within InteractiveRequestOptions).

@thomasback-dk Can you show you're NavigateToLogin call and how you constructed InteractiveRequestOptions? Is there a reason you couldn't add each key individually as a stringValue rather than adding a single extraQueryParams that's a Dictionary<string, string>?

If you cannot add each key individually, one workaround might be to serialize the Dictionary<string, string> to a JSON string manually using JsonSerializer.Serialize and deserialize manually using JsonSerializer.Deserialize after calling TryGetAdditionalParameter.

Changing your Dictionary<string, string> to Dictionary<string, object> might also work since that's already registered with the InteractiveRequestOptionsSerializerContext, but after deserializing all the object values in the nested dictionary value will probably be JsonElement's, so that might not be ideal.

The fact that TryAddAdditionalParameter<TValue>, and TryGetAdditionalParameter<TValue> don't work for all TValues and that there's no way to plug in your own custom types to the InteractiveRequestOptionsSerializerContext is indeed a problem, but I don't think it raises to the level of needing a fix patched into .NET 8 considering that there are workarounds and a proper fix will likely need new API to register custom types with the JsonSerializerContext assuming we don't just start using JsonOptions and do something similar to IdentityEndpointsJsonOptionsSetup to configure the needed types for source generation.

internal sealed class IdentityEndpointsJsonOptionsSetup : IConfigureOptions<JsonOptions>
{
public void Configure(JsonOptions options)
{
// Put our resolver in front of the reflection-based one. See ProblemDetailsOptionsSetup for a detailed explanation.
options.SerializerOptions.TypeInfoResolverChain.Insert(0, IdentityEndpointsJsonSerializerContext.Default);
}
}

Here is my approach calling NavigateToLogin as requested:

Navigation.NavigateToLogin("authentication/login", requestOptions);

Here is how I construct InteractiveRequestOptions

var additionalProviderParameters = new Dictionary<string, object>();

additionalProviderParameters.TryAdd("login_hint", "some_hint");

var requestOptions = new InteractiveRequestOptions
{
    Interaction = InteractionType.SignIn,
    ReturnUrl = "/"
};

requestOptions.TryAddAdditionalParameter("extraQueryParams", additionalProviderParameters);

It´s not possible to add each item individually in terms of OIDC specifications.

Above worked nicely in .NET 7 and stopped working when upgrading to .NET 8.

For now it´s working using the following workaround (mentioned here: #45028):

Blazor project's .csproj

<ItemGroup>
  <TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" />
</ItemGroup>

Content of TrimmerRootDescriptor.xml

<?xml version="1.0" encoding="UTF-8" ?>
<linker>
    <assembly fullname="Microsoft.AspNetCore.Components.WebAssembly.Authentication" preserve="all" />
</linker>

@Dennizzz
Copy link

My setup is slightly different:

var requestOptions = new InteractiveRequestOptions()
{
    Interaction = InteractionType.SignIn,
    ReturnUrl = _nav.Uri
};

requestOptions.TryAddAdditionalParameter("prompt", "select_account");
_nav.NavigateToLogin("authentication/login", requestOptions);

But the result is the same. This code works in debug mode, but in published release mode the prompt query param is ignored. It stopped working when .NET 7.0 was released, #45028 fixed it in a dot release and it broke again in 8.0. I also added the TrimmerRootDescriptor workaround again in my code as a mitigation.

@Dennizzz
Copy link

A maybe more severe effect of this issue is that ReturnUrl is stripped from the request as well.
Even the call without InteractiveRequestOptions() is affected (_nav.NavigateToLogin("authentication/login")).
After login, the user is always redirected to the root of my site, instead of the page the user tried to access unautorized.

@SteveSandersonMS SteveSandersonMS added this to the .NET 9 Planning milestone Feb 28, 2024
@Dennizzz
Copy link

#54225 probably fixes this when backported to 8.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug. feature-blazor-navigation Pillar: Technical Debt
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants