diff --git a/Directory.Build.targets b/Directory.Build.targets index 4bb0bb189ccd..1980cdcc77b2 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -3,13 +3,15 @@ true + $([MSBuild]::ValueOrDefault($(IsTrimmable),'false')) + . /// - public static Func CreateFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type implementation) + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe to use because implementation is either a KeyedHashAlgorithm or SymmetricAlgorithm type.")] + public static Func CreateFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type implementation) where T : class { return ((IActivator)Activator.CreateInstance(typeof(AlgorithmActivatorCore<>).MakeGenericType(implementation))!).Creator; } diff --git a/src/DataProtection/Extensions/src/DataProtectionProvider.cs b/src/DataProtection/Extensions/src/DataProtectionProvider.cs index 234661722b34..1cf6632495d7 100644 --- a/src/DataProtection/Extensions/src/DataProtectionProvider.cs +++ b/src/DataProtection/Extensions/src/DataProtectionProvider.cs @@ -172,6 +172,9 @@ internal static IDataProtectionProvider CreateProvider( setupAction(builder); // extract the provider instance from the service collection + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. return serviceCollection.BuildServiceProvider().GetRequiredService(); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. } } diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 3da1ac4e83a7..a67653f56b3d 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -33,6 +33,8 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action).MakeGenericType(typeof(HostBuilderContext), containerType); - - // Get the private ConfigureContainer method on this type then close over the container type - var configureCallback = typeof(GenericWebHostBuilder).GetMethod(nameof(ConfigureContainerImpl), BindingFlags.NonPublic | BindingFlags.Instance)! - .MakeGenericMethod(containerType) - .CreateDelegate(actionType, this); - - // _builder.ConfigureContainer(ConfigureContainer); - typeof(IHostBuilder).GetMethod(nameof(IHostBuilder.ConfigureContainer))! - .MakeGenericMethod(containerType) - .InvokeWithoutWrappingExceptions(_builder, new object[] { configureCallback }); + InvokeContainer(this, configureContainerBuilder); } // Resolve Configure after calling ConfigureServices and ConfigureContainer @@ -342,6 +334,30 @@ private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessi } }; }); + + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "There is a runtime check for ValueType startup container. It's unlikely anyone will use a ValueType here.")] + static void InvokeContainer(GenericWebHostBuilder genericWebHostBuilder, ConfigureContainerBuilder configureContainerBuilder) + { + var containerType = configureContainerBuilder.GetContainerType(); + + if (containerType.IsValueType && !RuntimeFeature.IsDynamicCodeSupported) + { + throw new InvalidOperationException("A ValueType TContainerBuilder isn't supported with AOT."); + } + + var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType); + + // Get the private ConfigureContainer method on this type then close over the container type + var configureCallback = typeof(GenericWebHostBuilder).GetMethod(nameof(ConfigureContainerImpl), BindingFlags.NonPublic | BindingFlags.Instance)! + .MakeGenericMethod(containerType) + .CreateDelegate(actionType, genericWebHostBuilder); + + // _builder.ConfigureContainer(ConfigureContainer); + typeof(IHostBuilder).GetMethod(nameof(IHostBuilder.ConfigureContainer))! + .MakeGenericMethod(containerType) + .InvokeWithoutWrappingExceptions(genericWebHostBuilder._builder, new object[] { configureCallback }); + } } private void ConfigureContainerImpl(HostBuilderContext context, TContainer container) where TContainer : notnull diff --git a/src/Hosting/Hosting/src/Internal/StartupLoader.cs b/src/Hosting/Hosting/src/Internal/StartupLoader.cs index ee23eda69c12..8a5332be0470 100644 --- a/src/Hosting/Hosting/src/Internal/StartupLoader.cs +++ b/src/Hosting/Hosting/src/Internal/StartupLoader.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.DependencyInjection; @@ -54,13 +55,26 @@ public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object); var builder = (ConfigureServicesDelegateBuilder)Activator.CreateInstance( - typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type), + CreateConfigureServicesDelegateBuilder(type), hostingServiceProvider, servicesMethod, configureContainerMethod, instance)!; return new StartupMethods(instance, configureMethod.Build(instance), builder.Build()); + + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "There is a runtime check for ValueType startup container. It's unlikely anyone will use a ValueType here.")] + static Type CreateConfigureServicesDelegateBuilder(Type type) + { + if (type.IsValueType && !RuntimeFeature.IsDynamicCodeSupported) + { + throw new InvalidOperationException("ValueType startup container isn't supported with AOT."); + } + + return typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type); + } } private abstract class ConfigureServicesDelegateBuilder @@ -146,7 +160,10 @@ IServiceProvider ConfigureServicesWithContainerConfiguration(IServiceCollection applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder); } + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. return applicationServiceProvider ?? services.BuildServiceProvider(); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. } } diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs index b2350b6ad006..b1804d2bb862 100644 --- a/src/Hosting/Hosting/src/Internal/WebHost.cs +++ b/src/Hosting/Hosting/src/Internal/WebHost.cs @@ -115,7 +115,10 @@ public void Initialize() // EnsureApplicationServices may have failed due to a missing or throwing Startup class. if (_applicationServices == null) { + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. _applicationServices = _applicationServiceCollection.BuildServiceProvider(); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. } if (!_options.CaptureStartupErrors) diff --git a/src/Hosting/Hosting/src/Startup/StartupBase.cs b/src/Hosting/Hosting/src/Startup/StartupBase.cs index c458273eab7e..7354dcf72c36 100644 --- a/src/Hosting/Hosting/src/Startup/StartupBase.cs +++ b/src/Hosting/Hosting/src/Startup/StartupBase.cs @@ -38,7 +38,10 @@ public virtual void ConfigureServices(IServiceCollection services) /// The . public virtual IServiceProvider CreateServiceProvider(IServiceCollection services) { + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. return services.BuildServiceProvider(); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. } } diff --git a/src/Hosting/Hosting/src/WebHostBuilder.cs b/src/Hosting/Hosting/src/WebHostBuilder.cs index c0db8b8734e3..dc46795d989d 100644 --- a/src/Hosting/Hosting/src/WebHostBuilder.cs +++ b/src/Hosting/Hosting/src/WebHostBuilder.cs @@ -200,7 +200,10 @@ public IWebHost Build() static IServiceProvider GetProviderFromFactory(IServiceCollection collection) { + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. var provider = collection.BuildServiceProvider(); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. var factory = provider.GetService>(); if (factory != null && factory is not DefaultServiceProviderFactory) diff --git a/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs index b6c63d8a77db..e7364bd09726 100644 --- a/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs +++ b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs @@ -216,7 +216,10 @@ public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hos { var options = new ServiceProviderOptions(); configure(context, options); + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. services.Replace(ServiceDescriptor.Singleton>(new DefaultServiceProviderFactory(options))); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. }); } diff --git a/src/Hosting/Hosting/test/Internal/MyContainer.cs b/src/Hosting/Hosting/test/Internal/MyContainer.cs index 434c616d3b6f..d2becf394968 100644 --- a/src/Hosting/Hosting/test/Internal/MyContainer.cs +++ b/src/Hosting/Hosting/test/Internal/MyContainer.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.DependencyInjection; diff --git a/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs b/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs index 2d01289cdf19..582154c971a8 100644 --- a/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs +++ b/src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Http; @@ -12,6 +13,8 @@ namespace Microsoft.AspNetCore.Mvc; [JsonConverter(typeof(ProblemDetailsJsonConverter))] public class ProblemDetails { + private readonly IDictionary _extensions = new Dictionary(StringComparer.Ordinal); + /// /// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when /// dereferenced, it provide human-readable documentation for the problem type @@ -59,5 +62,10 @@ public class ProblemDetails /// In particular, complex types or collection types may not round-trip to the original type when using the built-in JSON or XML formatters. /// [JsonExtensionData] - public IDictionary Extensions { get; } = new Dictionary(StringComparer.Ordinal); + public IDictionary Extensions + { + [RequiresUnreferencedCode("JSON serialization and deserialization of ProblemDetails.Extensions might require types that cannot be statically analyzed.")] + [RequiresDynamicCode("JSON serialization and deserialization of ProblemDetails.Extensions might require types that cannot be statically analyzed.")] + get => _extensions; + } } diff --git a/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs b/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs index 03982e3d303c..abf2896c88ee 100644 --- a/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs +++ b/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs @@ -818,12 +818,12 @@ public void Reset() } } + [RequiresUnreferencedCode("This API is not trim safe - from PropertyHelper")] internal sealed class PropertyStorage { public readonly object Value; public readonly PropertyHelper[] Properties; - [RequiresUnreferencedCode("This API is not trim safe.")] public PropertyStorage(object value) { Debug.Assert(value != null); diff --git a/src/Http/Http.Abstractions/test/HttpValidationProblemDetailsJsonConverterTest.cs b/src/Http/Http.Abstractions/test/HttpValidationProblemDetailsJsonConverterTest.cs index 06ca5f330c07..838bcd836243 100644 --- a/src/Http/Http.Abstractions/test/HttpValidationProblemDetailsJsonConverterTest.cs +++ b/src/Http/Http.Abstractions/test/HttpValidationProblemDetailsJsonConverterTest.cs @@ -11,6 +11,40 @@ public class HttpValidationProblemDetailsJsonConverterTest { private static JsonSerializerOptions JsonSerializerOptions => new JsonOptions().SerializerOptions; + [Fact] + public void Write_Works() + { + var converter = new HttpValidationProblemDetailsJsonConverter(); + var problemDetails = new HttpValidationProblemDetails(); + + problemDetails.Type = "https://tools.ietf.org/html/rfc9110#section-15.5.5"; + problemDetails.Title = "Not found"; + problemDetails.Status = 404; + problemDetails.Detail = "Product not found"; + problemDetails.Instance = "http://example.com/products/14"; + problemDetails.Extensions["traceId"] = "|37dd3dd5-4a9619f953c40a16."; + problemDetails.Errors.Add("key0", new[] { "error0" }); + problemDetails.Errors.Add("key1", new[] { "error1", "error2" }); + + var ms = new MemoryStream(); + var writer = new Utf8JsonWriter(ms); + converter.Write(writer, problemDetails, JsonSerializerOptions); + writer.Flush(); + + ms.Seek(0, SeekOrigin.Begin); + var document = JsonDocument.Parse(ms); + Assert.Equal(problemDetails.Type, document.RootElement.GetProperty("type").GetString()); + Assert.Equal(problemDetails.Title, document.RootElement.GetProperty("title").GetString()); + Assert.Equal(problemDetails.Status, document.RootElement.GetProperty("status").GetInt32()); + Assert.Equal(problemDetails.Detail, document.RootElement.GetProperty("detail").GetString()); + Assert.Equal(problemDetails.Instance, document.RootElement.GetProperty("instance").GetString()); + Assert.Equal((string)problemDetails.Extensions["traceId"]!, document.RootElement.GetProperty("traceId").GetString()); + var errorsElement = document.RootElement.GetProperty("errors"); + Assert.Equal("error0", errorsElement.GetProperty("key0")[0].GetString()); + Assert.Equal("error1", errorsElement.GetProperty("key1")[0].GetString()); + Assert.Equal("error2", errorsElement.GetProperty("key1")[1].GetString()); + } + [Fact] public void Read_Works() { diff --git a/src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs b/src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs index 2abf10254b5f..ea3e318a9a2d 100644 --- a/src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs +++ b/src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -48,17 +50,20 @@ public bool CanWrite(ProblemDetailsContext context) } [UnconditionalSuppressMessage("Trimming", "IL2026", - Justification = "JSON serialization of ProblemDetails.Extensions might require types that cannot be statically analyzed and we need to fallback" + - "to reflection-based. The ProblemDetailsConverter is marked as RequiresUnreferencedCode already.")] + Justification = "JSON serialization of ProblemDetails.Extensions might require types that cannot be statically analyzed. The property is annotated with RequiresUnreferencedCode.")] + [UnconditionalSuppressMessage("Trimming", "IL3050", + Justification = "JSON serialization of ProblemDetails.Extensions might require types that cannot be statically analyzed. The property is annotated with RequiresDynamicCode.")] public ValueTask WriteAsync(ProblemDetailsContext context) { var httpContext = context.HttpContext; ProblemDetailsDefaults.Apply(context.ProblemDetails, httpContext.Response.StatusCode); _options.CustomizeProblemDetails?.Invoke(context); - if (context.ProblemDetails.Extensions is { Count: 0 }) + // Use source generation serialization in two scenarios: + // 1. There are no extensions. Source generation is faster and works well with trimming. + // 2. Native AOT. In this case only the data types specified on ProblemDetailsJsonContext will work. + if (context.ProblemDetails.Extensions is { Count: 0 } || !RuntimeFeature.IsDynamicCodeSupported) { - // We can use the source generation in this case return new ValueTask(httpContext.Response.WriteAsJsonAsync( context.ProblemDetails, ProblemDetailsJsonContext.Default.ProblemDetails, @@ -71,7 +76,22 @@ public ValueTask WriteAsync(ProblemDetailsContext context) contentType: "application/problem+json")); } + // Additional values are specified on JsonSerializerContext to support some values for extensions. + // For example, the DeveloperExceptionMiddleware serializes its complex type to JsonElement, which problem details then needs to serialize. [JsonSerializable(typeof(ProblemDetails))] + [JsonSerializable(typeof(JsonElement))] + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(decimal))] + [JsonSerializable(typeof(float))] + [JsonSerializable(typeof(double))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(long))] + [JsonSerializable(typeof(Guid))] + [JsonSerializable(typeof(Uri))] + [JsonSerializable(typeof(TimeSpan))] + [JsonSerializable(typeof(DateTime))] + [JsonSerializable(typeof(DateTimeOffset))] internal sealed partial class ProblemDetailsJsonContext : JsonSerializerContext - { } + { + } } diff --git a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs index a204e74c0477..396400657154 100644 --- a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs +++ b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs @@ -21,6 +21,8 @@ public static class HttpRequestJsonExtensions { private const string RequiresUnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. " + "Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved."; + private const string RequiresDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed and need runtime code generation. " + + "Use the overload that takes a JsonTypeInfo or JsonSerializerContext for native AOT applications."; /// /// Read JSON from the request and deserialize to the specified type. @@ -31,6 +33,7 @@ public static class HttpRequestJsonExtensions /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static ValueTask ReadFromJsonAsync( this HttpRequest request, CancellationToken cancellationToken = default) @@ -48,6 +51,7 @@ public static class HttpRequestJsonExtensions /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static async ValueTask ReadFromJsonAsync( this HttpRequest request, JsonSerializerOptions? options, @@ -131,6 +135,7 @@ public static class HttpRequestJsonExtensions /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static ValueTask ReadFromJsonAsync( this HttpRequest request, Type type, @@ -149,6 +154,7 @@ public static class HttpRequestJsonExtensions /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static async ValueTask ReadFromJsonAsync( this HttpRequest request, Type type, diff --git a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs index 300b836a7d34..a4a9b2e80e49 100644 --- a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs +++ b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs @@ -18,6 +18,8 @@ public static partial class HttpResponseJsonExtensions { private const string RequiresUnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. " + "Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved."; + private const string RequiresDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed and need runtime code generation. " + + "Use the overload that takes a JsonTypeInfo or JsonSerializerContext for native AOT applications."; /// /// Write the specified value as JSON to the response body. The response content-type will be set to @@ -29,6 +31,7 @@ public static partial class HttpResponseJsonExtensions /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, TValue value, @@ -48,6 +51,7 @@ public static Task WriteAsJsonAsync( /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, TValue value, @@ -69,6 +73,7 @@ public static Task WriteAsJsonAsync( /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, TValue value, @@ -140,6 +145,7 @@ static async Task WriteAsJsonAsyncSlow(HttpResponse response, TValue value, Json } [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] private static async Task WriteAsJsonAsyncSlow( Stream body, TValue value, @@ -163,6 +169,7 @@ private static async Task WriteAsJsonAsyncSlow( /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, object? value, @@ -183,6 +190,7 @@ public static Task WriteAsJsonAsync( /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, object? value, @@ -205,6 +213,7 @@ public static Task WriteAsJsonAsync( /// A used to cancel the operation. /// The task object representing the asynchronous operation. [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] public static Task WriteAsJsonAsync( this HttpResponse response, object? value, @@ -236,6 +245,7 @@ public static Task WriteAsJsonAsync( } [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] + [RequiresDynamicCode(RequiresDynamicCodeMessage)] private static async Task WriteAsJsonAsyncSlow( Stream body, object? value, diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs index 21a98241225e..079d432979b0 100644 --- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs +++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs @@ -28,11 +28,8 @@ namespace Microsoft.AspNetCore.Http; /// /// Creates implementations from request handlers. /// -[UnconditionalSuppressMessage("Trimmer", "IL2026", Justification = "RequestDelegateFactory.Create requires unreferenced code.")] -[UnconditionalSuppressMessage("Trimmer", "IL2060", Justification = "RequestDelegateFactory.Create requires unreferenced code.")] -[UnconditionalSuppressMessage("Trimmer", "IL2072", Justification = "RequestDelegateFactory.Create requires unreferenced code.")] -[UnconditionalSuppressMessage("Trimmer", "IL2075", Justification = "RequestDelegateFactory.Create requires unreferenced code.")] -[UnconditionalSuppressMessage("Trimmer", "IL2077", Justification = "RequestDelegateFactory.Create requires unreferenced code.")] +[RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")] +[RequiresDynamicCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")] public static partial class RequestDelegateFactory { private static readonly ParameterBindingMethodCache ParameterBindingMethodCache = new(); @@ -124,7 +121,6 @@ public static partial class RequestDelegateFactory /// The for the route handler to be passed to . /// The options that will be used when calling . /// The to be passed to . - [RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")] public static RequestDelegateMetadataResult InferMetadata(MethodInfo methodInfo, RequestDelegateFactoryOptions? options = null) { var factoryContext = CreateFactoryContext(options); @@ -142,7 +138,6 @@ public static RequestDelegateMetadataResult InferMetadata(MethodInfo methodInfo, /// A request handler with any number of custom parameters that often produces a response with its return value. /// The used to configure the behavior of the handler. /// The . - [RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")] public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options) { return Create(handler, options, metadataResult: null); @@ -160,7 +155,6 @@ public static RequestDelegateResult Create(Delegate handler, RequestDelegateFact /// with that metadata. Otherwise, this metadata inference will be skipped as this step has already been done. /// /// The . - [RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")] [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")] public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null) { @@ -198,7 +192,6 @@ public static RequestDelegateResult Create(Delegate handler, RequestDelegateFact /// Creates the for the non-static method. /// The used to configure the behavior of the handler. /// The . - [RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")] public static RequestDelegateResult Create(MethodInfo methodInfo, Func? targetFactory, RequestDelegateFactoryOptions? options) { return Create(methodInfo, targetFactory, options, metadataResult: null); @@ -217,7 +210,6 @@ public static RequestDelegateResult Create(MethodInfo methodInfo, Func /// The . - [RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")] [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static RequestDelegateResult Create(MethodInfo methodInfo, Func? targetFactory = null, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null) { @@ -270,7 +262,7 @@ private static RequestDelegateFactoryContext CreateFactoryContext(RequestDelegat } var serviceProvider = options?.ServiceProvider ?? options?.EndpointBuilder?.ApplicationServices ?? EmptyServiceProvider.Instance; - var endpointBuilder = options?.EndpointBuilder ?? new RDFEndpointBuilder(serviceProvider); + var endpointBuilder = options?.EndpointBuilder ?? new RdfEndpointBuilder(serviceProvider); var jsonSerializerOptions = serviceProvider.GetService>()?.Value.SerializerOptions; var factoryContext = new RequestDelegateFactoryContext @@ -2533,9 +2525,9 @@ public Task ExecuteAsync(HttpContext httpContext) } } - private sealed class RDFEndpointBuilder : EndpointBuilder + private sealed class RdfEndpointBuilder : EndpointBuilder { - public RDFEndpointBuilder(IServiceProvider applicationServices) + public RdfEndpointBuilder(IServiceProvider applicationServices) { ApplicationServices = applicationServices; } diff --git a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs index 22e82eb81590..97f01d862be2 100644 --- a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs @@ -15,7 +15,8 @@ namespace Microsoft.AspNetCore.Builder; /// public static class EndpointRouteBuilderExtensions { - internal const string MapEndpointTrimmerWarning = "This API may perform reflection on the supplied delegate and its parameters. These types may be trimmed if not directly referenced."; + private const string MapEndpointUnreferencedCodeWarning = "This API may perform reflection on the supplied delegate and its parameters. These types may be trimmed if not directly referenced."; + private const string MapEndpointDynamicCodeWarning = "This API may perform reflection on the supplied delegate and its parameters. These types may require generated code and aren't compatible with native AOT applications."; // Avoid creating a new array every call private static readonly string[] GetVerb = new[] { HttpMethods.Get }; @@ -206,7 +207,8 @@ private static IEndpointConventionBuilder Map( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapGet( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -223,7 +225,8 @@ public static RouteHandlerBuilder MapGet( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapPost( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -240,7 +243,8 @@ public static RouteHandlerBuilder MapPost( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapPut( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -257,7 +261,8 @@ public static RouteHandlerBuilder MapPut( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapDelete( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -274,7 +279,8 @@ public static RouteHandlerBuilder MapDelete( /// The route pattern. /// The executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapPatch( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -292,7 +298,8 @@ public static RouteHandlerBuilder MapPatch( /// The delegate executed when the endpoint is matched. /// HTTP methods that the endpoint will match. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapMethods( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -311,7 +318,8 @@ public static RouteHandlerBuilder MapMethods( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder Map( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -328,7 +336,8 @@ public static RouteHandlerBuilder Map( /// The route pattern. /// The delegate executed when the endpoint is matched. /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, @@ -356,7 +365,8 @@ public static RouteHandlerBuilder Map( /// {*path:nonfile}. The order of the registered endpoint will be int.MaxValue. /// /// - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoints, Delegate handler) { return endpoints.MapFallback("{*path:nonfile}", handler); @@ -383,7 +393,8 @@ public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoin /// to exclude requests for static files. /// /// - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] public static RouteHandlerBuilder MapFallback( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, @@ -392,7 +403,8 @@ public static RouteHandlerBuilder MapFallback( return endpoints.Map(RoutePatternFactory.Parse(pattern), handler, httpMethods: null, isFallback: true); } - [RequiresUnreferencedCode(MapEndpointTrimmerWarning)] + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] private static RouteHandlerBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, diff --git a/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs index 173cfb59f468..d8c851bbd7dd 100644 --- a/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs @@ -36,7 +36,6 @@ public static class FallbackEndpointRouteBuilderExtensions /// {*path:nonfile}. The order of the registered endpoint will be int.MaxValue. /// /// - [RequiresUnreferencedCode(EndpointRouteBuilderExtensions.MapEndpointTrimmerWarning)] public static IEndpointConventionBuilder MapFallback(this IEndpointRouteBuilder endpoints, RequestDelegate requestDelegate) { ArgumentNullException.ThrowIfNull(endpoints); @@ -66,7 +65,6 @@ public static IEndpointConventionBuilder MapFallback(this IEndpointRouteBuilder /// to exclude requests for static files. /// /// - [RequiresUnreferencedCode(EndpointRouteBuilderExtensions.MapEndpointTrimmerWarning)] public static IEndpointConventionBuilder MapFallback( this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string pattern, diff --git a/src/Http/Routing/src/Matching/JumpTableBuilder.cs b/src/Http/Routing/src/Matching/JumpTableBuilder.cs index 0c48d3b261c2..11b013979a0a 100644 --- a/src/Http/Routing/src/Matching/JumpTableBuilder.cs +++ b/src/Http/Routing/src/Matching/JumpTableBuilder.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Routing.Matching; @@ -85,13 +86,15 @@ public static JumpTable Build(int defaultDestination, int exitDestination, (stri } // Use the ILEmitTrieJumpTable if the IL is going to be compiled (not interpreted) - if (RuntimeFeature.IsDynamicCodeCompiled) + return MakeILEmitTrieJumpTableIfSupported(defaultDestination, exitDestination, pathEntries, fallback); + + // TODO: This suppression can be removed when https://github.com/dotnet/linker/issues/2715 is complete. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Guarded by IsDynamicCodeCompiled")] + static JumpTable MakeILEmitTrieJumpTableIfSupported(int defaultDestination, int exitDestination, (string text, int destination)[] pathEntries, JumpTable fallback) { -#pragma warning disable IL3050 // See https://github.com/dotnet/linker/issues/2715. - return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback); -#pragma warning restore IL3050 + return RuntimeFeature.IsDynamicCodeCompiled + ? new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback) + : fallback; } - - return fallback; } } diff --git a/src/Http/Routing/src/RouteEndpointDataSource.cs b/src/Http/Routing/src/RouteEndpointDataSource.cs index 2e39cd81effb..1bacad5cd6f0 100644 --- a/src/Http/Routing/src/RouteEndpointDataSource.cs +++ b/src/Http/Routing/src/RouteEndpointDataSource.cs @@ -111,8 +111,6 @@ internal RouteEndpointBuilder GetSingleRouteEndpointBuilder() return CreateRouteEndpointBuilder(_routeEntries[0]); } - [UnconditionalSuppressMessage("Trimmer", "IL2026", - Justification = "We surface a RequireUnreferencedCode in the call to the Map method adding this EndpointDataSource. The trimmer is unable to infer this.")] private RouteEndpointBuilder CreateRouteEndpointBuilder( RouteEntry entry, RoutePattern? groupPrefix = null, IReadOnlyList>? groupConventions = null, IReadOnlyList>? groupFinallyConventions = null) { @@ -198,8 +196,8 @@ private RouteEndpointBuilder CreateRouteEndpointBuilder( // they can do so via IEndpointConventionBuilder.Finally like the do to override any other entry-specific metadata. if (isRouteHandler) { - rdfOptions = CreateRDFOptions(entry, pattern, builder); - rdfMetadataResult = RequestDelegateFactory.InferMetadata(entry.RouteHandler.Method, rdfOptions); + rdfOptions = CreateRdfOptions(entry, pattern, builder); + rdfMetadataResult = InferHandlerMetadata(entry.RouteHandler.Method, rdfOptions); } // Add delegate attributes as metadata before entry-specific conventions but after group conventions. @@ -223,11 +221,11 @@ private RouteEndpointBuilder CreateRouteEndpointBuilder( if (isRouteHandler || builder.FilterFactories.Count > 0) { - rdfOptions ??= CreateRDFOptions(entry, pattern, builder); + rdfOptions ??= CreateRdfOptions(entry, pattern, builder); // We ignore the returned EndpointMetadata has been already populated since we passed in non-null EndpointMetadata. // We always set factoryRequestDelegate in case something is still referencing the redirected version of the RequestDelegate. - factoryCreatedRequestDelegate = RequestDelegateFactory.Create(entry.RouteHandler, rdfOptions, rdfMetadataResult).RequestDelegate; + factoryCreatedRequestDelegate = CreateHandlerRequestDelegate(entry.RouteHandler, rdfOptions, rdfMetadataResult); } Debug.Assert(factoryCreatedRequestDelegate is not null); @@ -253,9 +251,31 @@ private RouteEndpointBuilder CreateRouteEndpointBuilder( } return builder; + + [UnconditionalSuppressMessage("Trimmer", "IL2026", + Justification = "We surface a RequireUnreferencedCode in the call to the Map methods adding route handlers to this EndpointDataSource. Analysis is unable to infer this. " + + "Map methods that configure a RequestDelegate don't use trimmer unsafe features.")] + [UnconditionalSuppressMessage("AOT", "IL3050", + Justification = "We surface a RequiresDynamicCode in the call to the Map methods adding route handlers this EndpointDataSource. Analysis is unable to infer this. " + + "Map methods that configure a RequestDelegate don't use AOT unsafe features.")] + static RequestDelegateMetadataResult InferHandlerMetadata(MethodInfo methodInfo, RequestDelegateFactoryOptions? options = null) + { + return RequestDelegateFactory.InferMetadata(methodInfo, options); + } + + [UnconditionalSuppressMessage("Trimmer", "IL2026", + Justification = "We surface a RequireUnreferencedCode in the call to the Map methods adding route handlers to this EndpointDataSource. Analysis is unable to infer this. " + + "Map methods that configure a RequestDelegate don't use trimmer unsafe features.")] + [UnconditionalSuppressMessage("AOT", "IL3050", + Justification = "We surface a RequiresDynamicCode in the call to the Map methods adding route handlers this EndpointDataSource. Analysis is unable to infer this. " + + "Map methods that configure a RequestDelegate don't use AOT unsafe features.")] + static RequestDelegate CreateHandlerRequestDelegate(Delegate handler, RequestDelegateFactoryOptions options, RequestDelegateMetadataResult? metadataResult) + { + return RequestDelegateFactory.Create(handler, options, metadataResult).RequestDelegate; + } } - private RequestDelegateFactoryOptions CreateRDFOptions(RouteEntry entry, RoutePattern pattern, RouteEndpointBuilder builder) + private RequestDelegateFactoryOptions CreateRdfOptions(RouteEntry entry, RoutePattern pattern, RouteEndpointBuilder builder) { var routeParamNames = new List(pattern.Parameters.Count); foreach (var parameter in pattern.Parameters) diff --git a/src/Identity/Core/src/IdentityBuilderExtensions.cs b/src/Identity/Core/src/IdentityBuilderExtensions.cs index a05ca35c002c..5a9d26d228d4 100644 --- a/src/Identity/Core/src/IdentityBuilderExtensions.cs +++ b/src/Identity/Core/src/IdentityBuilderExtensions.cs @@ -17,19 +17,20 @@ public static class IdentityBuilderExtensions /// /// The current instance. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because user type is a reference type.")] public static IdentityBuilder AddDefaultTokenProviders(this IdentityBuilder builder) { - var userType = builder.UserType; - var dataProtectionProviderType = typeof(DataProtectorTokenProvider<>).MakeGenericType(userType); - var phoneNumberProviderType = typeof(PhoneNumberTokenProvider<>).MakeGenericType(userType); - var emailTokenProviderType = typeof(EmailTokenProvider<>).MakeGenericType(userType); - var authenticatorProviderType = typeof(AuthenticatorTokenProvider<>).MakeGenericType(userType); + var dataProtectionProviderType = typeof(DataProtectorTokenProvider<>).MakeGenericType(builder.UserType); + var phoneNumberProviderType = typeof(PhoneNumberTokenProvider<>).MakeGenericType(builder.UserType); + var emailTokenProviderType = typeof(EmailTokenProvider<>).MakeGenericType(builder.UserType); + var authenticatorProviderType = typeof(AuthenticatorTokenProvider<>).MakeGenericType(builder.UserType); return builder.AddTokenProvider(TokenOptions.DefaultProvider, dataProtectionProviderType) .AddTokenProvider(TokenOptions.DefaultEmailProvider, emailTokenProviderType) .AddTokenProvider(TokenOptions.DefaultPhoneProvider, phoneNumberProviderType) .AddTokenProvider(TokenOptions.DefaultAuthenticatorProvider, authenticatorProviderType); } + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because user type is a reference type.")] private static void AddSignInManagerDeps(this IdentityBuilder builder) { builder.Services.AddHttpContextAccessor(); @@ -42,6 +43,7 @@ private static void AddSignInManagerDeps(this IdentityBuilder builder) /// /// The current instance. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because user type is a reference type.")] public static IdentityBuilder AddSignInManager(this IdentityBuilder builder) { builder.AddSignInManagerDeps(); @@ -56,6 +58,7 @@ public static IdentityBuilder AddSignInManager(this IdentityBuilder builder) /// The type of the sign in manager to add. /// The current instance. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because user type is a reference type.")] public static IdentityBuilder AddSignInManager<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSignInManager>(this IdentityBuilder builder) where TSignInManager : class { builder.AddSignInManagerDeps(); diff --git a/src/Identity/Extensions.Core/src/IdentityBuilder.cs b/src/Identity/Extensions.Core/src/IdentityBuilder.cs index 28e3804e9c93..b64111fc77af 100644 --- a/src/Identity/Extensions.Core/src/IdentityBuilder.cs +++ b/src/Identity/Extensions.Core/src/IdentityBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -21,6 +22,11 @@ public class IdentityBuilder /// The to attach to. public IdentityBuilder(Type user, IServiceCollection services) { + if (user.IsValueType) + { + throw new ArgumentException("User type can't be a value type.", nameof(user)); + } + UserType = user; Services = services; } @@ -32,7 +38,14 @@ public IdentityBuilder(Type user, IServiceCollection services) /// The to use for the roles. /// The to attach to. public IdentityBuilder(Type user, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type role, IServiceCollection services) : this(user, services) - => RoleType = role; + { + if (role.IsValueType) + { + throw new ArgumentException("Role type can't be a value type.", nameof(role)); + } + + RoleType = role; + } /// /// Gets the used for users. @@ -70,6 +83,7 @@ private IdentityBuilder AddScoped(Type serviceType, [DynamicallyAccessedMembers( /// /// The user validator type. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddUserValidator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>() where TValidator : class => AddScoped(typeof(IUserValidator<>).MakeGenericType(UserType), typeof(TValidator)); @@ -78,6 +92,7 @@ private IdentityBuilder AddScoped(Type serviceType, [DynamicallyAccessedMembers( /// /// The type of the claims principal factory. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddClaimsPrincipalFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactory>() where TFactory : class => AddScoped(typeof(IUserClaimsPrincipalFactory<>).MakeGenericType(UserType), typeof(TFactory)); @@ -97,6 +112,7 @@ private IdentityBuilder AddScoped(Type serviceType, [DynamicallyAccessedMembers( /// /// The validator type used to validate passwords. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddPasswordValidator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>() where TValidator : class => AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(TValidator)); @@ -105,6 +121,7 @@ private IdentityBuilder AddScoped(Type serviceType, [DynamicallyAccessedMembers( /// /// The user store type. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddUserStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TStore>() where TStore : class => AddScoped(typeof(IUserStore<>).MakeGenericType(UserType), typeof(TStore)); @@ -123,6 +140,7 @@ private IdentityBuilder AddScoped(Type serviceType, [DynamicallyAccessedMembers( /// The name of the provider to add. /// The type of the to add. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddTokenProvider(string providerName, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type provider) { if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).IsAssignableFrom(provider)) @@ -142,6 +160,7 @@ public virtual IdentityBuilder AddTokenProvider(string providerName, [Dynamicall /// /// The type of the user manager to add. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddUserManager<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TUserManager>() where TUserManager : class { var userManagerType = typeof(UserManager<>).MakeGenericType(UserType); @@ -162,6 +181,7 @@ public virtual IdentityBuilder AddTokenProvider(string providerName, [Dynamicall /// /// The role type. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddRoles<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TRole>() where TRole : class { RoleType = typeof(TRole); @@ -176,6 +196,7 @@ public virtual IdentityBuilder AddTokenProvider(string providerName, [Dynamicall /// /// The role validator type. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because RoleType is a reference type.")] public virtual IdentityBuilder AddRoleValidator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TRole>() where TRole : class { if (RoleType == null) @@ -206,6 +227,7 @@ public virtual IdentityBuilder AddTokenProvider(string providerName, [Dynamicall /// /// The role store. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because RoleType is a reference type.")] public virtual IdentityBuilder AddRoleStore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TStore>() where TStore : class { if (RoleType == null) @@ -220,6 +242,7 @@ public virtual IdentityBuilder AddTokenProvider(string providerName, [Dynamicall /// /// The type of the role manager to add. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because RoleType is a reference type.")] public virtual IdentityBuilder AddRoleManager<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TRoleManager>() where TRoleManager : class { if (RoleType == null) @@ -244,6 +267,7 @@ public virtual IdentityBuilder AddTokenProvider(string providerName, [Dynamicall /// /// The type of the user confirmation to add. /// The current instance. + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "MakeGenericType is safe because UserType is a reference type.")] public virtual IdentityBuilder AddUserConfirmation<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TUserConfirmation>() where TUserConfirmation : class => AddScoped(typeof(IUserConfirmation<>).MakeGenericType(UserType), typeof(TUserConfirmation)); } diff --git a/src/Identity/test/Identity.Test/IdentityBuilderTest.cs b/src/Identity/test/Identity.Test/IdentityBuilderTest.cs index 9b811d355ec6..faddd25e5c8b 100644 --- a/src/Identity/test/Identity.Test/IdentityBuilderTest.cs +++ b/src/Identity/test/Identity.Test/IdentityBuilderTest.cs @@ -26,6 +26,18 @@ public void AddRolesServicesAdded() Assert.IsType>(sp.GetRequiredService>()); } + [Fact] + public void IdentityBuilder_ValueTypeUser_Error() + { + Assert.Throws(() => new IdentityBuilder(typeof(int), new ServiceCollection())); + } + + [Fact] + public void IdentityBuilder_ValueTypeRole_Error() + { + Assert.Throws(() => new IdentityBuilder(typeof(PocoUser), typeof(int), new ServiceCollection())); + } + [Fact] public void AddRolesWithoutStoreWillError() { diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index ed6e7c663554..e565cce835e9 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -9,6 +9,7 @@ enable true $(DefineConstants);JS_INTEROP + false diff --git a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs index a3bb30343b05..1af140bb4787 100644 --- a/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs +++ b/src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs @@ -5,11 +5,14 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.RazorViews; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Json; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.FileProviders; @@ -34,6 +37,7 @@ internal class DeveloperExceptionPageMiddlewareImpl private readonly ExceptionDetailsProvider _exceptionDetailsProvider; private readonly Func _exceptionHandler; private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue("text/html"); + private readonly ExtensionsExceptionJsonContext _serializationContext; private readonly IProblemDetailsService? _problemDetailsService; /// @@ -45,6 +49,7 @@ internal class DeveloperExceptionPageMiddlewareImpl /// /// The used for writing diagnostic messages. /// The list of registered . + /// The used for serialization. /// The used for writing messages. public DeveloperExceptionPageMiddlewareImpl( RequestDelegate next, @@ -53,6 +58,7 @@ public DeveloperExceptionPageMiddlewareImpl( IWebHostEnvironment hostingEnvironment, DiagnosticSource diagnosticSource, IEnumerable filters, + IOptions? jsonOptions = null, IProblemDetailsService? problemDetailsService = null) { if (next == null) @@ -77,8 +83,8 @@ public DeveloperExceptionPageMiddlewareImpl( _diagnosticSource = diagnosticSource; _exceptionDetailsProvider = new ExceptionDetailsProvider(_fileProvider, _logger, _options.SourceCodeLineCount); _exceptionHandler = DisplayException; + _serializationContext = CreateSerializationContext(jsonOptions?.Value); _problemDetailsService = problemDetailsService; - foreach (var filter in filters.Reverse()) { var nextFilter = _exceptionHandler; @@ -86,6 +92,13 @@ public DeveloperExceptionPageMiddlewareImpl( } } + private static ExtensionsExceptionJsonContext CreateSerializationContext(JsonOptions? jsonOptions) + { + // Create context from configured options to get settings such as PropertyNamePolicy and DictionaryKeyPolicy. + jsonOptions ??= new JsonOptions(); + return new ExtensionsExceptionJsonContext(new JsonSerializerOptions(jsonOptions.SerializerOptions)); + } + /// /// Process an individual request. /// @@ -172,21 +185,7 @@ private async Task DisplayExceptionContent(ErrorContext errorContext) if (_problemDetailsService != null) { - var problemDetails = new ProblemDetails - { - Title = TypeNameHelper.GetTypeDisplayName(errorContext.Exception.GetType()), - Detail = errorContext.Exception.Message, - Status = httpContext.Response.StatusCode - }; - - problemDetails.Extensions["exception"] = new - { - Details = errorContext.Exception.ToString(), - Headers = httpContext.Request.Headers, - Path = httpContext.Request.Path.ToString(), - Endpoint = httpContext.GetEndpoint()?.ToString(), - RouteValues = httpContext.Features.Get()?.RouteValues, - }; + var problemDetails = CreateProblemDetails(errorContext, httpContext); await _problemDetailsService.WriteAsync(new() { @@ -214,6 +213,31 @@ await _problemDetailsService.WriteAsync(new() } } + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")] + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")] + private ProblemDetails CreateProblemDetails(ErrorContext errorContext, HttpContext httpContext) + { + var problemDetails = new ProblemDetails + { + Title = TypeNameHelper.GetTypeDisplayName(errorContext.Exception.GetType()), + Detail = errorContext.Exception.Message, + Status = httpContext.Response.StatusCode + }; + + // Problem details source gen serialization doesn't know about IHeaderDictionary or RouteValueDictionary. + // Serialize payload to a JsonElement here. Problem details serialization can write JsonElement in extensions dictionary. + problemDetails.Extensions["exception"] = JsonSerializer.SerializeToElement(new ExceptionExtensionData + ( + details: errorContext.Exception.ToString(), + headers: httpContext.Request.Headers, + path: httpContext.Request.Path.ToString(), + endpoint: httpContext.GetEndpoint()?.ToString(), + routeValues: httpContext.Features.Get()?.RouteValues + ), _serializationContext.ExceptionExtensionData); + + return problemDetails; + } + private Task DisplayCompilationException( HttpContext context, ICompilationException compilationException) @@ -328,3 +352,26 @@ private Task DisplayRuntimeException(HttpContext context, Exception ex) return errorPage.ExecuteAsync(context); } } + +internal sealed class ExceptionExtensionData +{ + public ExceptionExtensionData(string details, IHeaderDictionary headers, string path, string? endpoint, RouteValueDictionary? routeValues) + { + Details = details; + Headers = headers; + Path = path; + Endpoint = endpoint; + RouteValues = routeValues; + } + + public string Details { get; } + public IHeaderDictionary Headers { get; } + public string Path { get; } + public string? Endpoint { get; } + public RouteValueDictionary? RouteValues { get; } +} + +[JsonSerializable(typeof(ExceptionExtensionData))] +internal sealed partial class ExtensionsExceptionJsonContext : JsonSerializerContext +{ +} diff --git a/src/Middleware/Diagnostics/test/FunctionalTests/DeveloperExceptionPageSampleTest.cs b/src/Middleware/Diagnostics/test/FunctionalTests/DeveloperExceptionPageSampleTest.cs index 25770a6d51a1..ba9a28039657 100644 --- a/src/Middleware/Diagnostics/test/FunctionalTests/DeveloperExceptionPageSampleTest.cs +++ b/src/Middleware/Diagnostics/test/FunctionalTests/DeveloperExceptionPageSampleTest.cs @@ -6,6 +6,7 @@ using System.Net.Http.Headers; using Microsoft.AspNetCore.Mvc; using System.Net.Http.Json; +using System.Text.Json; namespace Microsoft.AspNetCore.Diagnostics.FunctionalTests; @@ -49,5 +50,14 @@ public async Task DeveloperExceptionPage_ShowsProblemDetails_WhenHtmlNotAccepted Assert.NotNull(body); Assert.Equal(500, body.Status); Assert.Contains("Demonstration exception", body.Detail); + + var exceptionNode = (JsonElement)body.Extensions["exception"]; + Assert.Contains("System.Exception: Demonstration exception.", exceptionNode.GetProperty("details").GetString()); + Assert.Equal("application/json", exceptionNode.GetProperty("headers").GetProperty("Accept")[0].GetString()); + Assert.Equal("localhost", exceptionNode.GetProperty("headers").GetProperty("Host")[0].GetString()); + Assert.Equal("/", exceptionNode.GetProperty("path").GetString()); + Assert.Equal("Endpoint display name", exceptionNode.GetProperty("endpoint").GetString()); + Assert.Equal("Value1", exceptionNode.GetProperty("routeValues").GetProperty("routeValue1").GetString()); + Assert.Equal("Value2", exceptionNode.GetProperty("routeValues").GetProperty("routeValue2").GetString()); } } diff --git a/src/Middleware/HealthChecks/src/HealthCheckOptions.cs b/src/Middleware/HealthChecks/src/HealthCheckOptions.cs index f813e47993ea..fdcb040d0822 100644 --- a/src/Middleware/HealthChecks/src/HealthCheckOptions.cs +++ b/src/Middleware/HealthChecks/src/HealthCheckOptions.cs @@ -49,7 +49,7 @@ public IDictionary ResultStatusCodes private static IDictionary ValidateStatusCodesMapping(IDictionary mapping) { - var missingHealthStatus = ((HealthStatus[])Enum.GetValues(typeof(HealthStatus))).Except(mapping.Keys).ToList(); + var missingHealthStatus = Enum.GetValues().Except(mapping.Keys).ToList(); if (missingHealthStatus.Count > 0) { var missing = string.Join(", ", missingHealthStatus.Select(status => $"{nameof(HealthStatus)}.{status}")); diff --git a/src/Middleware/Spa/SpaProxy/src/Microsoft.AspNetCore.SpaProxy.csproj b/src/Middleware/Spa/SpaProxy/src/Microsoft.AspNetCore.SpaProxy.csproj index 1807fe067e83..b2ebc472c1fa 100644 --- a/src/Middleware/Spa/SpaProxy/src/Microsoft.AspNetCore.SpaProxy.csproj +++ b/src/Middleware/Spa/SpaProxy/src/Microsoft.AspNetCore.SpaProxy.csproj @@ -4,6 +4,7 @@ Helpers for launching the SPA CLI proxy automatically when the application starts in ASP.NET MVC Core. $(DefaultNetCoreTargetFramework) true + false diff --git a/src/Mvc/Mvc.Core/test/Infrastructure/ValidationProblemDetailsJsonConverterTest.cs b/src/Mvc/Mvc.Core/test/Infrastructure/ValidationProblemDetailsJsonConverterTest.cs index b7b877bf38f0..df3ef0d94cd3 100644 --- a/src/Mvc/Mvc.Core/test/Infrastructure/ValidationProblemDetailsJsonConverterTest.cs +++ b/src/Mvc/Mvc.Core/test/Infrastructure/ValidationProblemDetailsJsonConverterTest.cs @@ -148,7 +148,7 @@ public void WriteWorks() using Utf8JsonWriter writer = new(stream); // Act - converter.Write(writer, problemDetails, null); + converter.Write(writer, problemDetails, new JsonSerializerOptions()); writer.Flush(); var json = Encoding.UTF8.GetString(stream.ToArray()); diff --git a/src/Shared/EndpointMetadataPopulator.cs b/src/Shared/EndpointMetadataPopulator.cs index bf36507d9d0c..c17dbf818d59 100644 --- a/src/Shared/EndpointMetadataPopulator.cs +++ b/src/Shared/EndpointMetadataPopulator.cs @@ -11,7 +11,8 @@ namespace Microsoft.AspNetCore.Http; -[UnconditionalSuppressMessage("Trimmer", "IL2060", Justification = "Trimmer warnings are presented in RequestDelegateFactory.")] +[RequiresUnreferencedCode("This API performs reflection on types that can't be statically analyzed.")] +[RequiresDynamicCode("This API performs reflection on types that can't be statically analyzed.")] internal static class EndpointMetadataPopulator { private static readonly MethodInfo PopulateMetadataForParameterMethod = typeof(EndpointMetadataPopulator).GetMethod(nameof(PopulateMetadataForParameter), BindingFlags.NonPublic | BindingFlags.Static)!; diff --git a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs index d47f97473557..4951c218b9a8 100644 --- a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs +++ b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs @@ -29,6 +29,7 @@ public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType AwaitableInfo = coercedAwaitableInfo; } + [RequiresDynamicCode("Dynamically generates calls to FSharpAsync.")] public static bool IsTypeAwaitable( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, out CoercedAwaitableInfo info) diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs index 52272f420834..8767cc2b4b7c 100644 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs @@ -10,6 +10,8 @@ namespace Microsoft.Extensions.Internal; +[RequiresUnreferencedCode("ObjectMethodExecutor performs reflection on arbitrary types.")] +[RequiresDynamicCode("ObjectMethodExecutor performs reflection on arbitrary types.")] internal sealed class ObjectMethodExecutor { private readonly object?[]? _parameterDefaultValues; @@ -26,7 +28,6 @@ internal sealed class ObjectMethodExecutor typeof(Action) // unsafeOnCompletedMethod })!; - [RequiresUnreferencedCode("This method performs reflection on arbitrary types.")] private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object?[]? parameterDefaultValues) { if (methodInfo == null) @@ -76,13 +77,11 @@ private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, obj public bool IsMethodAsync { get; } - [RequiresUnreferencedCode("This method performs reflection on arbitrary types.")] public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) { return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null); } - [RequiresUnreferencedCode("This method performs reflection on arbitrary types.")] public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object?[] parameterDefaultValues) { if (parameterDefaultValues == null) diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs index 5b2a7389d2d6..95970685f6cb 100644 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs @@ -22,6 +22,7 @@ namespace Microsoft.Extensions.Internal; /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references /// to FSharp types have to be constructed dynamically at runtime. /// +[RequiresDynamicCode("Dynamically generates calls to FSharpAsync.")] internal static class ObjectMethodExecutorFSharpSupport { private static readonly object _fsharpValuesCacheLock = new object(); @@ -30,7 +31,7 @@ internal static class ObjectMethodExecutorFSharpSupport private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; - [UnconditionalSuppressMessage("Trimmer", "IL2060", Justification = "Reflecting over the async FSharpAsync<> contract")] + [UnconditionalSuppressMessage("Trimmer", "IL2060", Justification = "Reflecting over the async FSharpAsync<> contract.")] public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( Type possibleFSharpAsyncType, out Expression coerceToAwaitableExpression, diff --git a/src/Shared/ParameterBindingMethodCache.cs b/src/Shared/ParameterBindingMethodCache.cs index 64a994b593b8..c0461a115dc8 100644 --- a/src/Shared/ParameterBindingMethodCache.cs +++ b/src/Shared/ParameterBindingMethodCache.cs @@ -54,6 +54,7 @@ public ParameterBindingMethodCache(bool preferNonGenericEnumParseOverload, bool } [RequiresUnreferencedCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] + [RequiresDynamicCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] public bool HasTryParseMethod(Type type) { var nonNullableParameterType = Nullable.GetUnderlyingType(type) ?? type; @@ -61,10 +62,12 @@ public bool HasTryParseMethod(Type type) } [RequiresUnreferencedCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] + [RequiresDynamicCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] public bool HasBindAsyncMethod(ParameterInfo parameter) => FindBindAsyncMethod(parameter).Expression is not null; [RequiresUnreferencedCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] + [RequiresDynamicCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] public Func? FindTryParseMethod(Type type) { // This method is used to find TryParse methods from .NET types using reflection. It's used at app runtime. @@ -195,6 +198,7 @@ static bool ValidateReturnType(MethodInfo methodInfo) } [RequiresUnreferencedCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] + [RequiresDynamicCode("Performs reflection on type hierarchy. This cannot be statically analyzed.")] public (Expression? Expression, int ParamCount) FindBindAsyncMethod(ParameterInfo parameter) { (Func?, int) Finder(Type nonNullableParameterType) @@ -394,6 +398,7 @@ static bool ValidateReturnType(MethodInfo methodInfo) throw new InvalidOperationException($"No public parameterless constructor found for type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}'."); } + [RequiresDynamicCode("MakeGenericMethod is possible used with ValueTypes and isn't compatible with AOT.")] private static MethodInfo? GetIBindableFromHttpContextMethod(Type type) { // Check if parameter is bindable via static abstract method on IBindableFromHttpContext diff --git a/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs b/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs index 95b0a9cdcda4..d903bf99c34d 100644 --- a/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs +++ b/src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // 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; @@ -11,14 +10,12 @@ internal sealed class HttpValidationProblemDetailsJsonConverter : JsonConverter< { private static readonly JsonEncodedText Errors = JsonEncodedText.Encode("errors"); - [UnconditionalSuppressMessage("Trimmer", "IL2026", Justification = "Trimmer does not allow annotating overriden methods with annotations different from the ones in base type.")] public override HttpValidationProblemDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var problemDetails = new HttpValidationProblemDetails(); return ReadProblemDetails(ref reader, options, problemDetails); } - [RequiresUnreferencedCode("JSON serialization and deserialization ProblemDetails.Extensions might require types that cannot be statically analyzed. ")] public static HttpValidationProblemDetails ReadProblemDetails(ref Utf8JsonReader reader, JsonSerializerOptions options, HttpValidationProblemDetails problemDetails) { if (reader.TokenType != JsonTokenType.StartObject) @@ -30,14 +27,7 @@ public static HttpValidationProblemDetails ReadProblemDetails(ref Utf8JsonReader { if (reader.ValueTextEquals(Errors.EncodedUtf8Bytes)) { - var errors = DeserializeErrors(ref reader, options); - if (errors is not null) - { - foreach (var item in errors) - { - problemDetails.Errors[item.Key] = item.Value; - } - } + ReadErrors(ref reader, problemDetails.Errors); } else { @@ -52,32 +42,87 @@ public static HttpValidationProblemDetails ReadProblemDetails(ref Utf8JsonReader return problemDetails; - [UnconditionalSuppressMessage("Trimmer", "IL2026", Justification = "We ensure Dictionary is preserved.")] - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties, typeof(Dictionary))] - static Dictionary? DeserializeErrors(ref Utf8JsonReader reader, JsonSerializerOptions options) - => JsonSerializer.Deserialize>(ref reader, options); + static void ReadErrors(ref Utf8JsonReader reader, IDictionary errors) + { + if (!reader.Read()) + { + throw new JsonException("Unexpected end when reading JSON."); + } + + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + var name = reader.GetString()!; + + if (!reader.Read()) + { + throw new JsonException("Unexpected end when reading JSON."); + } + + if (reader.TokenType == JsonTokenType.Null) + { + errors[name] = null!; + } + else + { + var values = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + values.Add(reader.GetString()!); + } + errors[name] = values.ToArray(); + } + } + break; + case JsonTokenType.Null: + return; + default: + throw new JsonException($"Unexpected token when reading errors: {reader.TokenType}"); + } + } } - [UnconditionalSuppressMessage("Trimmer", "IL2026", Justification = "Trimmer does not allow annotating overriden methods with annotations different from the ones in base type.")] public override void Write(Utf8JsonWriter writer, HttpValidationProblemDetails value, JsonSerializerOptions options) { WriteProblemDetails(writer, value, options); } - [RequiresUnreferencedCode("JSON serialization and deserialization ProblemDetails.Extensions might require types that cannot be statically analyzed. ")] public static void WriteProblemDetails(Utf8JsonWriter writer, HttpValidationProblemDetails value, JsonSerializerOptions options) { writer.WriteStartObject(); ProblemDetailsJsonConverter.WriteProblemDetails(writer, value, options); writer.WritePropertyName(Errors); - SerializeErrors(writer, value.Errors, options); + WriteErrors(writer, value, options); writer.WriteEndObject(); - [UnconditionalSuppressMessage("Trimmer", "IL2026", Justification = "We ensure IDictionary is preserved.")] - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(IDictionary))] - static void SerializeErrors(Utf8JsonWriter writer, IDictionary errors, JsonSerializerOptions options) - => JsonSerializer.Serialize(writer, errors, options); + static void WriteErrors(Utf8JsonWriter writer, HttpValidationProblemDetails value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + foreach (var kvp in value.Errors) + { + var name = kvp.Key; + var errors = kvp.Value; + + writer.WritePropertyName(options.DictionaryKeyPolicy?.ConvertName(name) ?? name); + if (errors is null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStartArray(); + foreach (var error in errors) + { + writer.WriteStringValue(error); + } + writer.WriteEndArray(); + } + } + writer.WriteEndObject(); + } } } diff --git a/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs b/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs index 28369eb5e0a2..13fa59608f2b 100644 --- a/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs +++ b/src/Shared/ProblemDetails/ProblemDetailsJsonConverter.cs @@ -16,7 +16,6 @@ internal sealed class ProblemDetailsJsonConverter : JsonConverter TValue - return MakeFastPropertyGetter( - typeof(ByRefFunc<,>), - getMethod, - propertyGetterByRefWrapperMethod); + // TODO: Remove disable when https://github.com/dotnet/linker/issues/2715 is complete. +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + if (getMethod.DeclaringType!.IsValueType) + { + // Create a delegate (ref TDeclaringType) -> TValue + return MakeFastPropertyGetter( + typeof(ByRefFunc<,>), + getMethod, + propertyGetterByRefWrapperMethod); + } + else + { + // Create a delegate TDeclaringType -> TValue + return MakeFastPropertyGetter( + typeof(Func<,>), + getMethod, + propertyGetterWrapperMethod); + } +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. } else { - // Create a delegate TDeclaringType -> TValue - return MakeFastPropertyGetter( - typeof(Func<,>), - getMethod, - propertyGetterWrapperMethod); + return propertyInfo.GetValue; } } [RequiresUnreferencedCode("This API is not trimmer safe.")] + [RequiresDynamicCode("This API requires dynamic code because it makes generic types which may be filled with ValueTypes.")] private static Func MakeFastPropertyGetter( Type openGenericDelegateType, MethodInfo propertyGetMethod, @@ -283,22 +285,32 @@ public static PropertyHelper[] GetVisibleProperties( var parameters = setMethod.GetParameters(); Debug.Assert(parameters.Length == 1); - // Instance methods in the CLR can be turned into static methods where the first parameter - // is open over "target". This parameter is always passed by reference, so we have a code - // path for value types and a code path for reference types. - var typeInput = setMethod.DeclaringType!; - var parameterType = parameters[0].ParameterType; - - // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } - var propertySetterAsAction = - setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); - var callPropertySetterClosedGenericMethod = - CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); - var callPropertySetterDelegate = - callPropertySetterClosedGenericMethod.CreateDelegate( - typeof(Action), propertySetterAsAction); - - return (Action)callPropertySetterDelegate; + if (RuntimeFeature.IsDynamicCodeSupported) + { + // TODO: Remove disable when https://github.com/dotnet/linker/issues/2715 is complete. +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + var typeInput = setMethod.DeclaringType!; + var parameterType = parameters[0].ParameterType; + + // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } + var propertySetterAsAction = + setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); + var callPropertySetterClosedGenericMethod = + CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); + var callPropertySetterDelegate = + callPropertySetterClosedGenericMethod.CreateDelegate( + typeof(Action), propertySetterAsAction); + + return (Action)callPropertySetterDelegate; +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + } + else + { + return propertyInfo.SetValue; + } } ///