Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -59,4 +61,27 @@ public static IHybridCacheBuilder AddSerializerFactory<
_ = Throw.IfNull(builder).Services.AddSingleton<IHybridCacheSerializerFactory, TImplementation>();
return builder;
}

/// <summary>
/// Register a default <see cref="JsonSerializerOptions"/> for use with JSON serialization.
/// </summary>
/// <returns>The <see cref="IHybridCacheBuilder"/> instance.</returns>
[Experimental(DiagnosticIds.Experiments.HybridCache, UrlFormat = DiagnosticIds.UrlFormat)]
public static IHybridCacheBuilder WithJsonSerializerOptions(this IHybridCacheBuilder builder, JsonSerializerOptions options)
{
_ = Throw.IfNull(builder).Services.AddKeyedSingleton<JsonSerializerOptions>(typeof(IHybridCacheSerializer<>), Throw.IfNull(options));
return builder;
}

/// <summary>
/// Register a <see cref="JsonSerializerOptions"/> for use with JSON serialization of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type being serialized.</typeparam>
/// <returns>The <see cref="IHybridCacheBuilder"/> instance.</returns>
[Experimental(DiagnosticIds.Experiments.HybridCache, UrlFormat = DiagnosticIds.UrlFormat)]
public static IHybridCacheBuilder WithJsonSerializerOptions<T>(this IHybridCacheBuilder builder, JsonSerializerOptions options)
{
_ = Throw.IfNull(builder).Services.AddKeyedSingleton<JsonSerializerOptions>(typeof(IHybridCacheSerializer<T>), Throw.IfNull(options));
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;

[assembly: UnconditionalSuppressMessage("AOT", "IL2026", Justification = "Checked at runtime, guidance issued")]
[assembly: UnconditionalSuppressMessage("AOT", "IL2070", Justification = "Checked at runtime, guidance issued")]
[assembly: UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Checked at runtime, guidance issued")]

namespace Microsoft.Extensions.Caching.Hybrid.Internal;

internal sealed class DefaultJsonSerializerFactory : IHybridCacheSerializerFactory
Expand All @@ -34,7 +38,7 @@ public bool TryCreateSerializer<T>([NotNullWhen(true)] out IHybridCacheSerialize

// see if there is a per-type options registered (keyed by the **closed** generic type), otherwise use the default
JsonSerializerOptions options = _serviceProvider.GetKeyedService<JsonSerializerOptions>(typeof(IHybridCacheSerializer<T>)) ?? Options;
if (!options.IncludeFields && ReferenceEquals(options, SystemDefaultJsonOptions) && IsFieldOnlyType(typeof(T)))
if (!options.IncludeFields && IsDefaultJsonOptions(options) && IsFieldOnlyType(typeof(T)))
{
// value-tuples expose fields, not properties; special-case this as a common scenario
options = FieldEnabledJsonOptions;
Expand All @@ -50,11 +54,42 @@ internal static bool IsFieldOnlyType(Type type)
return IsFieldOnlyType(type, ref state) == FieldOnlyResult.FieldOnly;
}

private static bool IsDefaultJsonOptions(JsonSerializerOptions options)
{
#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
{
// can't be, since we don't use default options for AOT
return false;
}
#endif

#pragma warning disable IDE0079 // unnecessary suppression: TFM-dependent
#pragma warning disable IL2026, IL3050 // AOT bits
return ReferenceEquals(options, JsonSerializerOptions.Default);
#pragma warning restore IL2026, IL3050
#pragma warning restore IDE0079
}

private static JsonSerializerOptions SystemDefaultJsonOptions
{
get
{
#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
{
throw new NotSupportedException($"When using AOT, {nameof(JsonSerializerOptions)} with {nameof(JsonSerializerOptions.TypeInfoResolver)} specified must be provided via"
+ $" {nameof(IHybridCacheBuilder)}.{nameof(HybridCacheBuilderExtensions.WithJsonSerializerOptions)}.");
}
#endif

#pragma warning disable IDE0079 // unnecessary suppression: TFM-dependent
#pragma warning disable IL2026, IL3050 // AOT bits
private static JsonSerializerOptions SystemDefaultJsonOptions => JsonSerializerOptions.Default;
return JsonSerializerOptions.Default;
#pragma warning restore IL2026, IL3050
#pragma warning restore IDE0079
}
}

[SuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.",
Justification = "Custom serializers may be needed for AOT with STJ")]
Expand Down
1 change: 1 addition & 0 deletions src/Shared/DiagnosticIds/DiagnosticIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal static class Experiments
internal const string DocumentDb = "EXTEXP0011";
internal const string AutoActivation = "EXTEXP0012";
internal const string HttpLogging = "EXTEXP0013";
internal const string HybridCache = "EXTEXP0018";
}

internal static class LoggerMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,20 +236,20 @@ public class NodeB<T>
private static T RoundTrip<T>(T value, ReadOnlySpan<byte> expectedBytes, JsonSerializer expectedJsonOptions, JsonSerializer addSerializers = JsonSerializer.None, bool binary = false)
{
var services = new ServiceCollection();
services.AddHybridCache();
var hc = services.AddHybridCache();
JsonSerializerOptions? globalOptions = null;
JsonSerializerOptions? perTypeOptions = null;

if ((addSerializers & JsonSerializer.CustomGlobal) != JsonSerializer.None)
{
globalOptions = new() { IncludeFields = true }; // assume any custom options will serialize the whole type
services.AddKeyedSingleton<JsonSerializerOptions>(typeof(IHybridCacheSerializer<>), globalOptions);
hc.WithJsonSerializerOptions(globalOptions);
}

if ((addSerializers & JsonSerializer.CustomPerType) != JsonSerializer.None)
{
perTypeOptions = new() { IncludeFields = true }; // assume any custom options will serialize the whole type
services.AddKeyedSingleton<JsonSerializerOptions>(typeof(IHybridCacheSerializer<T>), perTypeOptions);
hc.WithJsonSerializerOptions<T>(perTypeOptions);
}

JsonSerializerOptions? expectedOptionsObj = expectedJsonOptions switch
Expand Down
Loading