Skip to content

Commit

Permalink
Improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
MackinnonBuck committed Apr 16, 2024
1 parent 17f9810 commit d758ef8
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" />
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsCache.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\HotReloadManager.cs" LinkBase="HotReload" />
<Compile Include="$(SharedSourceRoot)LinkerFlags.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)QueryStringEnumerable.cs" LinkBase="Shared" />
Expand Down
29 changes: 0 additions & 29 deletions src/Components/Components/src/PersistentComponentState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using static Microsoft.AspNetCore.Internal.LinkerFlags;

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

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

internal PersistentComponentState(
IDictionary<string , byte[]> currentState,
Expand Down Expand Up @@ -116,33 +114,6 @@ public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> call
}
}

/// <summary>
/// Tries to retrieve the persisted state as JSON with the given <paramref name="key"/> and deserializes it into an
/// instance of type <typeparamref name="TValue"/>.
/// </summary>
/// <param name="key">The key used to persist the instance.</param>
/// <param name="resolver">The <see cref="IJsonTypeInfoResolver"/> to use when deserializing from JSON.</param>
/// <param name="instance">The persisted instance.</param>
/// <returns><c>true</c> if the state was found; <c>false</c> otherwise.</returns>
[RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")]
public bool TryTakeFromJson<TValue>(string key, IJsonTypeInfoResolver resolver, [MaybeNullWhen(false)] out TValue? instance)
{
ArgumentNullException.ThrowIfNull(key);

if (TryTake(key, out var data))
{
var reader = new Utf8JsonReader(data);
var options = _jsonSerializerOptionsCache.GetOrAdd(resolver);
instance = JsonSerializer.Deserialize<TValue>(ref reader, options)!;
return true;
}
else
{
instance = default;
return false;
}
}

private bool TryTake(string key, out byte[]? value)
{
ArgumentNullException.ThrowIfNull(key);
Expand Down
1 change: 0 additions & 1 deletion src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
#nullable enable
Microsoft.AspNetCore.Components.PersistentComponentState.TryTakeFromJson<TValue>(string! key, System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver! resolver, out TValue? instance) -> bool
16 changes: 11 additions & 5 deletions src/Components/Shared/src/DefaultAntiforgeryStateProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.Web;

Expand All @@ -25,14 +26,19 @@ public DefaultAntiforgeryStateProvider(PersistentComponentState state)
// don't have access to the request.
_subscription = state.RegisterOnPersisting(() =>
{
state.PersistAsJson(PersistenceKey, GetAntiforgeryToken());
var bytes = JsonSerializer.SerializeToUtf8Bytes(
GetAntiforgeryToken(),
DefaultAntiforgeryStateProviderSerializerContext.Default.AntiforgeryRequestToken);
state.PersistAsJson(PersistenceKey, bytes);
return Task.CompletedTask;
}, RenderMode.InteractiveAuto);

state.TryTakeFromJson(
PersistenceKey,
DefaultAntiforgeryStateProviderSerializerContext.Default,
out _currentToken);
if (state.TryTakeFromJson<byte[]>(PersistenceKey, out var bytes))
{
_currentToken = JsonSerializer.Deserialize(
bytes,
DefaultAntiforgeryStateProviderSerializerContext.Default.AntiforgeryRequestToken);
}
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

namespace Microsoft.AspNetCore.Components;

// For custom converters that don't rely on serializing an object graph,
// we can resolve the incoming type's JsonTypeInfo directly from the converter.
// This skips extra work to collect metadata for the type that won't be used.
internal sealed class JsonConverterFactoryTypeInfoResolver<T> : IJsonTypeInfoResolver
{
public static readonly JsonConverterFactoryTypeInfoResolver<T> Instance = new();

private JsonConverterFactoryTypeInfoResolver()
{
}

public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
{
if (type != typeof(T))
{
return null;
}

foreach (var converter in options.Converters)
{
if (converter is not JsonConverterFactory factory || !factory.CanConvert(type))
{
continue;
}

if (factory.CreateConverter(type, options) is not { } converterToUse)
{
continue;
}

return JsonMetadataServices.CreateValueInfo<T>(options, converterToUse);
}

return null;
}
}
75 changes: 0 additions & 75 deletions src/Components/Shared/src/JsonSerializerOptionsCache.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<Compile Include="$(ComponentsSharedSourceRoot)src\AttributeUtilities.cs" LinkBase="Forms" />
<Compile Include="$(ComponentsSharedSourceRoot)src\ExpressionFormatting\**\*.cs" LinkBase="Forms\ExpressionFommatting" />
<Compile Include="$(ComponentsSharedSourceRoot)src\DefaultAntiforgeryStateProvider.cs" LinkBase="Forms" />
<Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerialization\JsonConverterFactoryTypeInfoResolver.cs" LinkBase="JsonSerialization" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 8 additions & 1 deletion src/Components/Web/src/WebRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,15 @@ public WebRenderer(

// Supply a DotNetObjectReference to JS that it can use to call us back for events etc.
jsComponentInterop.AttachToRenderer(this);

var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var jsRuntimeJsonSerializerOptions = jsRuntime.CloneJsonSerializerOptions();
jsRuntimeJsonSerializerOptions.TypeInfoResolverChain.Insert(0, JsonConverterFactoryTypeInfoResolver<DotNetObjectReference<WebRendererInteropMethods>>.Instance);
jsRuntimeJsonSerializerOptions.TypeInfoResolverChain.Insert(0, WebRendererSerializerContext.Default);

jsRuntime.InvokeVoidAsync(
"Blazor._internal.attachWebRendererInterop",
WebRendererSerializerContext.Default,
jsRuntimeJsonSerializerOptions,
_rendererId,
_interopMethodsReference,
jsComponentInterop.Configuration.JSComponentParametersByIdentifier,
Expand Down Expand Up @@ -148,6 +153,8 @@ public void RemoveRootComponent(int componentId)
}
}

[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(object[]))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(Dictionary<string, JSComponentConfigurationStore.JSComponentParameter[]>))]
[JsonSerializable(typeof(Dictionary<string, List<string>>))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
});
}
},
configureRuntime: (builder) => {
builder.withConfig({
browserProfilerOptions: {},
});
},
},
}).then(() => {
const startedParagraph = document.createElement('p');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<StaticWebAssetBasePath>WasmMinimal</StaticWebAssetBasePath>

<WasmProfilers>browser;</WasmProfilers>
<WasmBuildNative>true</WasmBuildNative>
</PropertyGroup>

<ItemGroup>
Expand Down
21 changes: 14 additions & 7 deletions src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json;
using static Microsoft.AspNetCore.Internal.LinkerFlags;

namespace Microsoft.JSInterop;
Expand Down Expand Up @@ -34,11 +34,11 @@ public interface IJSRuntime
/// </summary>
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
/// <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>
/// <param name="resolver">The <see cref="IJsonTypeInfoResolver"/> to use for JSON serialization and deserialization.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for JSON serialization and deserialization.</param>
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, IJsonTypeInfoResolver resolver, object?[]? args)
=> throw new InvalidOperationException($"Supplying a custom {nameof(IJsonTypeInfoResolver)} is not supported by the current JS runtime");
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, JsonSerializerOptions options, object?[]? args)
=> throw new InvalidOperationException($"Supplying a custom {nameof(JsonSerializerOptions)} is not supported by the current JS runtime");

/// <summary>
/// Invokes the specified JavaScript function asynchronously.
Expand All @@ -58,13 +58,20 @@ public interface IJSRuntime
/// </summary>
/// <typeparam name="TValue">The JSON-serializable return type.</typeparam>
/// <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>
/// <param name="resolver">The <see cref="IJsonTypeInfoResolver"/> to use for JSON serialization and deserialization.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for JSON serialization and deserialization.</param>
/// <param name="cancellationToken">
/// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts
/// (<see cref="JSRuntime.DefaultAsyncTimeout"/>) from being applied.
/// </param>
/// <param name="args">JSON-serializable arguments.</param>
/// <returns>An instance of <typeparamref name="TValue"/> obtained by JSON-deserializing the return value.</returns>
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, IJsonTypeInfoResolver resolver, CancellationToken cancellationToken, object?[]? args)
=> throw new InvalidOperationException($"Supplying a custom {nameof(IJsonTypeInfoResolver)} is not supported by the current JS runtime");
ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(JsonSerialized)] TValue>(string identifier, JsonSerializerOptions options, CancellationToken cancellationToken, object?[]? args)
=> throw new InvalidOperationException($"Supplying a custom {nameof(JsonSerializerOptions)} is not supported by the current JS runtime");

/// <summary>
/// Returns a copy of the current <see cref="JsonSerializerOptions"/> used for JSON serialization and deserialization.
/// </summary>
/// <returns>A copy of the <see cref="JsonSerializerOptions"/>.</returns>
JsonSerializerOptions CloneJsonSerializerOptions()
=> throw new InvalidOperationException($"The current JS runtime does not support cloning {nameof(JsonSerializerOptions)}");
}
Loading

0 comments on commit d758ef8

Please sign in to comment.