Skip to content

Commit d758ef8

Browse files
committed
Improvements
1 parent 17f9810 commit d758ef8

15 files changed

+132
-166
lines changed

src/Components/Components/src/Microsoft.AspNetCore.Components.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
<ItemGroup>
1717
<Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" />
1818
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" />
19-
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsCache.cs" />
2019
<Compile Include="$(ComponentsSharedSourceRoot)src\HotReloadManager.cs" LinkBase="HotReload" />
2120
<Compile Include="$(SharedSourceRoot)LinkerFlags.cs" LinkBase="Shared" />
2221
<Compile Include="$(SharedSourceRoot)QueryStringEnumerable.cs" LinkBase="Shared" />

src/Components/Components/src/PersistentComponentState.cs

-29
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Diagnostics.CodeAnalysis;
55
using System.Text.Json;
6-
using System.Text.Json.Serialization.Metadata;
76
using static Microsoft.AspNetCore.Internal.LinkerFlags;
87

98
namespace Microsoft.AspNetCore.Components;
@@ -17,7 +16,6 @@ public class PersistentComponentState
1716
private readonly IDictionary<string, byte[]> _currentState;
1817

1918
private readonly List<PersistComponentStateRegistration> _registeredCallbacks;
20-
private readonly JsonSerializerOptionsCache _jsonSerializerOptionsCache = new(JsonSerializerOptionsProvider.Options);
2119

2220
internal PersistentComponentState(
2321
IDictionary<string , byte[]> currentState,
@@ -116,33 +114,6 @@ public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> call
116114
}
117115
}
118116

119-
/// <summary>
120-
/// Tries to retrieve the persisted state as JSON with the given <paramref name="key"/> and deserializes it into an
121-
/// instance of type <typeparamref name="TValue"/>.
122-
/// </summary>
123-
/// <param name="key">The key used to persist the instance.</param>
124-
/// <param name="resolver">The <see cref="IJsonTypeInfoResolver"/> to use when deserializing from JSON.</param>
125-
/// <param name="instance">The persisted instance.</param>
126-
/// <returns><c>true</c> if the state was found; <c>false</c> otherwise.</returns>
127-
[RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")]
128-
public bool TryTakeFromJson<TValue>(string key, IJsonTypeInfoResolver resolver, [MaybeNullWhen(false)] out TValue? instance)
129-
{
130-
ArgumentNullException.ThrowIfNull(key);
131-
132-
if (TryTake(key, out var data))
133-
{
134-
var reader = new Utf8JsonReader(data);
135-
var options = _jsonSerializerOptionsCache.GetOrAdd(resolver);
136-
instance = JsonSerializer.Deserialize<TValue>(ref reader, options)!;
137-
return true;
138-
}
139-
else
140-
{
141-
instance = default;
142-
return false;
143-
}
144-
}
145-
146117
private bool TryTake(string key, out byte[]? value)
147118
{
148119
ArgumentNullException.ThrowIfNull(key);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Components.PersistentComponentState.TryTakeFromJson<TValue>(string! key, System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver! resolver, out TValue? instance) -> bool

src/Components/Shared/src/DefaultAntiforgeryStateProvider.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics.CodeAnalysis;
5+
using System.Text.Json;
56
using System.Text.Json.Serialization;
67
using Microsoft.AspNetCore.Components.Web;
78

@@ -25,14 +26,19 @@ public DefaultAntiforgeryStateProvider(PersistentComponentState state)
2526
// don't have access to the request.
2627
_subscription = state.RegisterOnPersisting(() =>
2728
{
28-
state.PersistAsJson(PersistenceKey, GetAntiforgeryToken());
29+
var bytes = JsonSerializer.SerializeToUtf8Bytes(
30+
GetAntiforgeryToken(),
31+
DefaultAntiforgeryStateProviderSerializerContext.Default.AntiforgeryRequestToken);
32+
state.PersistAsJson(PersistenceKey, bytes);
2933
return Task.CompletedTask;
3034
}, RenderMode.InteractiveAuto);
3135

32-
state.TryTakeFromJson(
33-
PersistenceKey,
34-
DefaultAntiforgeryStateProviderSerializerContext.Default,
35-
out _currentToken);
36+
if (state.TryTakeFromJson<byte[]>(PersistenceKey, out var bytes))
37+
{
38+
_currentToken = JsonSerializer.Deserialize(
39+
bytes,
40+
DefaultAntiforgeryStateProviderSerializerContext.Default.AntiforgeryRequestToken);
41+
}
3642
}
3743

3844
/// <inheritdoc />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
using System.Text.Json.Serialization.Metadata;
7+
8+
namespace Microsoft.AspNetCore.Components;
9+
10+
// For custom converters that don't rely on serializing an object graph,
11+
// we can resolve the incoming type's JsonTypeInfo directly from the converter.
12+
// This skips extra work to collect metadata for the type that won't be used.
13+
internal sealed class JsonConverterFactoryTypeInfoResolver<T> : IJsonTypeInfoResolver
14+
{
15+
public static readonly JsonConverterFactoryTypeInfoResolver<T> Instance = new();
16+
17+
private JsonConverterFactoryTypeInfoResolver()
18+
{
19+
}
20+
21+
public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
22+
{
23+
if (type != typeof(T))
24+
{
25+
return null;
26+
}
27+
28+
foreach (var converter in options.Converters)
29+
{
30+
if (converter is not JsonConverterFactory factory || !factory.CanConvert(type))
31+
{
32+
continue;
33+
}
34+
35+
if (factory.CreateConverter(type, options) is not { } converterToUse)
36+
{
37+
continue;
38+
}
39+
40+
return JsonMetadataServices.CreateValueInfo<T>(options, converterToUse);
41+
}
42+
43+
return null;
44+
}
45+
}

src/Components/Shared/src/JsonSerializerOptionsCache.cs

-75
This file was deleted.

src/Components/Web/src/Microsoft.AspNetCore.Components.Web.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<Compile Include="$(ComponentsSharedSourceRoot)src\AttributeUtilities.cs" LinkBase="Forms" />
1919
<Compile Include="$(ComponentsSharedSourceRoot)src\ExpressionFormatting\**\*.cs" LinkBase="Forms\ExpressionFommatting" />
2020
<Compile Include="$(ComponentsSharedSourceRoot)src\DefaultAntiforgeryStateProvider.cs" LinkBase="Forms" />
21+
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerialization\JsonConverterFactoryTypeInfoResolver.cs" LinkBase="JsonSerialization" />
2122
</ItemGroup>
2223

2324
<ItemGroup>

src/Components/Web/src/WebRenderer.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,15 @@ public WebRenderer(
4141

4242
// Supply a DotNetObjectReference to JS that it can use to call us back for events etc.
4343
jsComponentInterop.AttachToRenderer(this);
44+
4445
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
46+
var jsRuntimeJsonSerializerOptions = jsRuntime.CloneJsonSerializerOptions();
47+
jsRuntimeJsonSerializerOptions.TypeInfoResolverChain.Insert(0, JsonConverterFactoryTypeInfoResolver<DotNetObjectReference<WebRendererInteropMethods>>.Instance);
48+
jsRuntimeJsonSerializerOptions.TypeInfoResolverChain.Insert(0, WebRendererSerializerContext.Default);
49+
4550
jsRuntime.InvokeVoidAsync(
4651
"Blazor._internal.attachWebRendererInterop",
47-
WebRendererSerializerContext.Default,
52+
jsRuntimeJsonSerializerOptions,
4853
_rendererId,
4954
_interopMethodsReference,
5055
jsComponentInterop.Configuration.JSComponentParametersByIdentifier,
@@ -148,6 +153,8 @@ public void RemoveRootComponent(int componentId)
148153
}
149154
}
150155

156+
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
157+
[JsonSerializable(typeof(object[]))]
151158
[JsonSerializable(typeof(int))]
152159
[JsonSerializable(typeof(Dictionary<string, JSComponentConfigurationStore.JSComponentParameter[]>))]
153160
[JsonSerializable(typeof(Dictionary<string, List<string>>))]

src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor

+5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@
7070
});
7171
}
7272
},
73+
configureRuntime: (builder) => {
74+
builder.withConfig({
75+
browserProfilerOptions: {},
76+
});
77+
},
7378
},
7479
}).then(() => {
7580
const startedParagraph = document.createElement('p');

src/Components/test/testassets/Components.WasmMinimal/Components.WasmMinimal.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<StaticWebAssetBasePath>WasmMinimal</StaticWebAssetBasePath>
8+
9+
<WasmProfilers>browser;</WasmProfilers>
10+
<WasmBuildNative>true</WasmBuildNative>
811
</PropertyGroup>
912

1013
<ItemGroup>

src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs

+14-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics.CodeAnalysis;
5-
using System.Text.Json.Serialization.Metadata;
5+
using System.Text.Json;
66
using static Microsoft.AspNetCore.Internal.LinkerFlags;
77

88
namespace Microsoft.JSInterop;
@@ -34,11 +34,11 @@ public interface IJSRuntime
3434
/// </summary>
3535
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
3636
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
37-
/// <param name="resolver">The <see cref="IJsonTypeInfoResolver"/> to use for JSON serialization and deserialization.</param>
37+
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for JSON serialization and deserialization.</param>
3838
/// <param name="args">JSON-serializable arguments.</param>
3939
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
40-
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, IJsonTypeInfoResolver resolver, object?[]? args)
41-
=> throw new InvalidOperationException($"Supplying a custom {nameof(IJsonTypeInfoResolver)} is not supported by the current JS runtime");
40+
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, JsonSerializerOptions options, object?[]? args)
41+
=> throw new InvalidOperationException($"Supplying a custom {nameof(JsonSerializerOptions)} is not supported by the current JS runtime");
4242

4343
/// <summary>
4444
/// Invokes the specified JavaScript function asynchronously.
@@ -58,13 +58,20 @@ public interface IJSRuntime
5858
/// </summary>
5959
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
6060
/// <param name="identifier">An identifier for the function to invoke. For example, the value <c>"someScope.someFunction"</c> will invoke the function <c>window.someScope.someFunction</c>.</param>
61-
/// <param name="resolver">The <see cref="IJsonTypeInfoResolver"/> to use for JSON serialization and deserialization.</param>
61+
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for JSON serialization and deserialization.</param>
6262
/// <param name="cancellationToken">
6363
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
6464
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
6565
/// </param>
6666
/// <param name="args">JSON-serializable arguments.</param>
6767
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
68-
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, IJsonTypeInfoResolver resolver, CancellationToken cancellationToken, object?[]? args)
69-
=> throw new InvalidOperationException($"Supplying a custom {nameof(IJsonTypeInfoResolver)} is not supported by the current JS runtime");
68+
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, JsonSerializerOptions options, CancellationToken cancellationToken, object?[]? args)
69+
=> throw new InvalidOperationException($"Supplying a custom {nameof(JsonSerializerOptions)} is not supported by the current JS runtime");
70+
71+
/// <summary>
72+
/// Returns a copy of the current <see cref="JsonSerializerOptions"/> used for JSON serialization and deserialization.
73+
/// </summary>
74+
/// <returns>A copy of the <see cref="JsonSerializerOptions"/>.</returns>
75+
JsonSerializerOptions CloneJsonSerializerOptions()
76+
=> throw new InvalidOperationException($"The current JS runtime does not support cloning {nameof(JsonSerializerOptions)}");
7077
}

0 commit comments

Comments
 (0)