diff options
author | Pranav K <prkrishn@hotmail.com> | 2022-03-31 02:29:17 +0300 |
---|---|---|
committer | James Newton-King <james@newtonking.com> | 2022-03-31 11:20:32 +0300 |
commit | 6b6b39b857e2b2602e8f089e0fab8713a409c6e8 (patch) | |
tree | c326dffb3fd7c6475e7dd37679f5648251a108a4 | |
parent | cef52ffd679ff3a1966cce3f9782f13496fff672 (diff) |
Trim Microsoft.AspNetCore.Hostingjamesnk/trimming-hosting-full
17 files changed, 213 insertions, 90 deletions
diff --git a/eng/TrimmableProjects.props b/eng/TrimmableProjects.props index f6db6bf40d..46255cf5f6 100644 --- a/eng/TrimmableProjects.props +++ b/eng/TrimmableProjects.props @@ -8,14 +8,17 @@ <Project> <ItemGroup> <TrimmableProject Include="Microsoft.AspNetCore.Hosting.Abstractions" /> + <TrimmableProject Include="Microsoft.AspNetCore.Hosting" /> <TrimmableProject Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" /> <TrimmableProject Include="Microsoft.Net.Http.Headers" /> <TrimmableProject Include="Microsoft.AspNetCore.Http.Abstractions" /> <TrimmableProject Include="Microsoft.AspNetCore.Http.Extensions" /> <TrimmableProject Include="Microsoft.AspNetCore.Http.Features" /> + <TrimmableProject Include="Microsoft.AspNetCore.Http" /> <TrimmableProject Include="Microsoft.AspNetCore.Metadata" /> <TrimmableProject Include="Microsoft.AspNetCore.Routing.Abstractions" /> <TrimmableProject Include="Microsoft.AspNetCore.WebUtilities" /> + <TrimmableProject Include="Microsoft.AspNetCore.Connections.Abstractions" /> <TrimmableProject Include="Microsoft.AspNetCore.Authorization" /> <TrimmableProject Include="Microsoft.AspNetCore.Components.Authorization" /> <TrimmableProject Include="Microsoft.AspNetCore.Components" /> diff --git a/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs b/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs index 1645ddd3fb..36ce56ce49 100644 --- a/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs +++ b/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs @@ -49,7 +49,7 @@ public static class HostingAbstractionsWebHostBuilderExtensions /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param> /// <param name="startupAssemblyName">The name of the assembly containing the startup type.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> - [RequiresUnreferencedCode("Types and members the loaded assembly depends on might be removed.")] + [RequiresUnreferencedCode("Finding the startup type in an assembly isn't support. Specify the startup type explicitly in configuration.")] public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName) { if (startupAssemblyName == null) diff --git a/src/Hosting/Hosting.slnf b/src/Hosting/Hosting.slnf index 5c7fc50293..0ef4e18cea 100644 --- a/src/Hosting/Hosting.slnf +++ b/src/Hosting/Hosting.slnf @@ -2,6 +2,7 @@ "solution": { "path": "..\\..\\AspNetCore.sln", "projects": [ + "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", "src\\FileProviders\\Embedded\\src\\Microsoft.Extensions.FileProviders.Embedded.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", @@ -17,7 +18,6 @@ "src\\Hosting\\test\\FunctionalTests\\Microsoft.AspNetCore.Hosting.FunctionalTests.csproj", "src\\Hosting\\test\\testassets\\IStartupInjectionAssemblyName\\IStartupInjectionAssemblyName.csproj", "src\\Hosting\\test\\testassets\\TestStartupAssembly1\\TestStartupAssembly1.csproj", - "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", @@ -30,6 +30,7 @@ "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", "src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", "src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj", + "src\\Servers\\Kestrel\\Transport.Quic\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj", "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", "src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj" ] diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs index ac601aab49..9998cf8da0 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs @@ -97,28 +97,34 @@ internal sealed class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, // Support UseStartup(assemblyName) if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly)) { - try - { - var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName); - UseStartup(startupType, context, services); - } - catch (Exception ex) when (webHostOptions.CaptureStartupErrors) - { - var capture = ExceptionDispatchInfo.Capture(ex); - - services.Configure<GenericWebHostServiceOptions>(options => - { - options.ConfigureApplication = app => - { - // Throw if there was any errors initializing startup - capture.Throw(); - }; - }); - } + ScanAssemblyAndRegisterStartup(context, services, webhostContext, webHostOptions); } }); } + [UnconditionalSuppressMessage("Trimmer", "IL2072", Justification = "Finding startup type in assembly requires unreferenced code. Surfaced to user in UseStartup(assemblyName).")] + private void ScanAssemblyAndRegisterStartup(HostBuilderContext context, IServiceCollection services, WebHostBuilderContext webhostContext, WebHostOptions webHostOptions) + { + try + { + var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly!, webhostContext.HostingEnvironment.EnvironmentName); + UseStartup(startupType, context, services); + } + catch (Exception ex) when (webHostOptions.CaptureStartupErrors) + { + var capture = ExceptionDispatchInfo.Capture(ex); + + services.Configure<GenericWebHostServiceOptions>(options => + { + options.ConfigureApplication = app => + { + // Throw if there was any errors initializing startup + capture.Throw(); + }; + }); + } + } + private void ExecuteHostingStartups() { var webHostOptions = new WebHostOptions(_config); @@ -219,19 +225,22 @@ internal sealed class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, // UseStartup can be called multiple times. Only run the last one. _startupObject = startupType; + var state = new UseStartupState(startupType); + _builder.ConfigureServices((context, services) => { // Run this delegate if the startup type matches - if (object.ReferenceEquals(_startupObject, startupType)) + if (object.ReferenceEquals(_startupObject, state.StartupType)) { - UseStartup(startupType, context, services); + UseStartup(state.StartupType, context, services); } }); return this; } - public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory) + [RequiresUnreferencedCode("Startup type created by factory can't be determined statically. Specify the startup type explicitly in configuration.")] + public IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory) { var startupAssemblyName = startupFactory.GetMethodInfo().DeclaringType!.Assembly.GetName().Name; @@ -240,7 +249,10 @@ internal sealed class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, // Clear the startup type _startupObject = startupFactory; - _builder.ConfigureServices((context, services) => + _builder.ConfigureServices(ConfigureStartup); + + [UnconditionalSuppressMessage("Trimmer", "IL2072", Justification = "Startup type created by factory can't be determined statically.")] + void ConfigureStartup(HostBuilderContext context, IServiceCollection services) { // UseStartup can be called multiple times. Only run the last one. if (object.ReferenceEquals(_startupObject, startupFactory)) @@ -249,7 +261,7 @@ internal sealed class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, var instance = startupFactory(webHostBuilderContext) ?? throw new InvalidOperationException("The specified factory returned null startup instance."); UseStartup(instance.GetType(), context, services, instance); } - }); + } return this; } diff --git a/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs index 54b1271bcc..294e3ca6b8 100644 --- a/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs @@ -83,7 +83,8 @@ internal sealed class HostingStartupWebHostBuilder : IWebHostBuilder, ISupportsS return _builder.UseStartup(startupType); } - public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory) + [RequiresUnreferencedCode("Startup type created by factory can't be determined statically. Specify the startup type explicitly in configuration.")] + public IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory) { return _builder.UseStartup(startupFactory); } diff --git a/src/Hosting/Hosting/src/Infrastructure/ISupportsStartup.cs b/src/Hosting/Hosting/src/Infrastructure/ISupportsStartup.cs index cf4dedf93e..bc67d4a8ee 100644 --- a/src/Hosting/Hosting/src/Infrastructure/ISupportsStartup.cs +++ b/src/Hosting/Hosting/src/Infrastructure/ISupportsStartup.cs @@ -41,5 +41,6 @@ public interface ISupportsStartup /// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> /// <remarks>When using the IL linker, all public methods of <typeparamref name="TStartup"/> are preserved. This should match the Startup type directly (and not a base type).</remarks> - IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory); + [RequiresUnreferencedCode("Startup type created by factory can't be determined statically. Specify the startup type explicitly in configuration.")] + IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory); } diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 2085fd970b..32183ebc84 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Http; @@ -221,41 +222,83 @@ internal class HostingApplicationDiagnostics } } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", + Justification = "The values being passed into Write have the commonly used properties being preserved with DynamicDependency.")] + private static void WriteDiagnosticEvent<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TValue>( + DiagnosticSource diagnosticSource, string name, TValue value) + { + diagnosticSource.Write(name, value); + } + [MethodImpl(MethodImplOptions.NoInlining)] private void RecordBeginRequestDiagnostics(HttpContext httpContext, long startTimestamp) { - _diagnosticListener.Write( + WriteDiagnosticEvent( + _diagnosticListener, DeprecatedDiagnosticsBeginRequestKey, - new - { - httpContext = httpContext, - timestamp = startTimestamp - }); + new DeprecatedRequestData(httpContext, startTimestamp)); } [MethodImpl(MethodImplOptions.NoInlining)] private void RecordEndRequestDiagnostics(HttpContext httpContext, long currentTimestamp) { - _diagnosticListener.Write( + WriteDiagnosticEvent( + _diagnosticListener, DeprecatedDiagnosticsEndRequestKey, - new - { - httpContext = httpContext, - timestamp = currentTimestamp - }); + new DeprecatedRequestData(httpContext, currentTimestamp)); } [MethodImpl(MethodImplOptions.NoInlining)] private void RecordUnhandledExceptionDiagnostics(HttpContext httpContext, long currentTimestamp, Exception exception) { - _diagnosticListener.Write( + WriteDiagnosticEvent( + _diagnosticListener, DiagnosticsUnhandledExceptionKey, - new - { - httpContext = httpContext, - timestamp = currentTimestamp, - exception = exception - }); + new UnhandledExceptionData(httpContext, currentTimestamp, exception)); + } + + private sealed class DeprecatedRequestData + { + // Common properties. Properties not in this list could be trimmed. + [DynamicDependency(nameof(HttpContext.Request), typeof(HttpContext))] + [DynamicDependency(nameof(HttpContext.Response), typeof(HttpContext))] + [DynamicDependency(nameof(HttpRequest.Path), typeof(HttpRequest))] + [DynamicDependency(nameof(HttpRequest.Method), typeof(HttpRequest))] + [DynamicDependency(nameof(HttpResponse.StatusCode), typeof(HttpResponse))] + internal DeprecatedRequestData(HttpContext httpContext, long timestamp) + { + this.httpContext = httpContext; + this.timestamp = timestamp; + } + + // Compatibility with anonymous object property names + public HttpContext httpContext { get; } + public long timestamp { get; } + + public override string ToString() => $"{{ {nameof(httpContext)} = {httpContext}, {nameof(timestamp)} = {timestamp} }}"; + } + + private sealed class UnhandledExceptionData + { + // Common properties. Properties not in this list could be trimmed. + [DynamicDependency(nameof(HttpContext.Request), typeof(HttpContext))] + [DynamicDependency(nameof(HttpContext.Response), typeof(HttpContext))] + [DynamicDependency(nameof(HttpRequest.Path), typeof(HttpRequest))] + [DynamicDependency(nameof(HttpRequest.Method), typeof(HttpRequest))] + [DynamicDependency(nameof(HttpResponse.StatusCode), typeof(HttpResponse))] + internal UnhandledExceptionData(HttpContext httpContext, long timestamp, Exception exception) + { + this.httpContext = httpContext; + this.timestamp = timestamp; + this.exception = exception; + } + + // Compatibility with anonymous object property names + public HttpContext httpContext { get; } + public long timestamp { get; } + public Exception exception { get; } + + public override string ToString() => $"{{ {nameof(httpContext)} = {httpContext}, {nameof(timestamp)} = {timestamp}, {nameof(exception)} = {exception} }}"; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -347,7 +390,7 @@ internal class HostingApplicationDiagnostics private Activity StartActivity(Activity activity, HttpContext httpContext) { activity.Start(); - _diagnosticListener.Write(ActivityStartKey, httpContext); + WriteDiagnosticEvent(_diagnosticListener, ActivityStartKey, httpContext); return activity; } @@ -359,7 +402,7 @@ internal class HostingApplicationDiagnostics { activity.SetEndTime(DateTime.UtcNow); } - _diagnosticListener.Write(ActivityStopKey, httpContext); + WriteDiagnosticEvent(_diagnosticListener, ActivityStopKey, httpContext); activity.Stop(); // Resets Activity.Current (we want this after the Write) } diff --git a/src/Hosting/Hosting/src/Internal/UseStartupState.cs b/src/Hosting/Hosting/src/Internal/UseStartupState.cs new file mode 100644 index 0000000000..954feaea4d --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/UseStartupState.cs @@ -0,0 +1,18 @@ +// 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; + +namespace Microsoft.AspNetCore.Hosting.Internal; + +// Workaround for linker bug: https://github.com/dotnet/linker/issues/1981 +internal readonly struct UseStartupState +{ + public UseStartupState([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType) + { + StartupType = startupType; + } + + [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] + public Type StartupType { get; } +} diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj index ffd5b45ef3..3d90a8102d 100644 --- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <Description>ASP.NET Core hosting infrastructure and startup logic for web applications.</Description> @@ -8,6 +8,7 @@ <PackageTags>aspnetcore;hosting</PackageTags> <IsPackable>false</IsPackable> <Nullable>enable</Nullable> + <Trimmable>true</Trimmable> </PropertyGroup> <ItemGroup> diff --git a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs b/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs index f71b8f65c7..ff097eca5e 100644 --- a/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs +++ b/src/Hosting/Hosting/src/StaticWebAssets/StaticWebAssetsLoader.cs @@ -46,7 +46,7 @@ public class StaticWebAssetsLoader { try { - var candidate = configuration.GetValue<string>(WebHostDefaults.StaticWebAssetsKey) ?? ResolveRelativeToAssembly(environment); + var candidate = configuration[WebHostDefaults.StaticWebAssetsKey] ?? ResolveRelativeToAssembly(environment); if (candidate != null && File.Exists(candidate)) { return File.OpenRead(candidate); diff --git a/src/Hosting/Hosting/src/WebHostBuilder.cs b/src/Hosting/Hosting/src/WebHostBuilder.cs index 0fb6538653..2598ae6f08 100644 --- a/src/Hosting/Hosting/src/WebHostBuilder.cs +++ b/src/Hosting/Hosting/src/WebHostBuilder.cs @@ -301,38 +301,47 @@ public class WebHostBuilder : IWebHostBuilder if (!string.IsNullOrEmpty(_options.StartupAssembly)) { - try + ScanAssemblyAndRegisterStartup(services, _options.StartupAssembly); + } + + _configureServices?.Invoke(_context, services); + + return services; + } + + [UnconditionalSuppressMessage("Trimmer", "IL2077", Justification = "Finding startup type in assembly requires unreferenced code. Surfaced to user in UseStartup(startupAssemblyName).")] + private void ScanAssemblyAndRegisterStartup(ServiceCollection services, string startupAssemblyName) + { + try + { + var startupType = StartupLoader.FindStartupType(startupAssemblyName, _hostingEnvironment.EnvironmentName); + + if (typeof(IStartup).IsAssignableFrom(startupType)) + { + services.AddSingleton(typeof(IStartup), startupType); + } + else { - var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName); + services.AddSingleton(typeof(IStartup), RegisterStartup); - if (typeof(IStartup).IsAssignableFrom(startupType)) - { - services.AddSingleton(typeof(IStartup), startupType); - } - else + [UnconditionalSuppressMessage("Trimmer", "IL2077", Justification = "Finding startup type in assembly requires unreferenced code. Surfaced to user in UseStartup(startupAssemblyName).")] + object RegisterStartup(IServiceProvider serviceProvider) { - services.AddSingleton(typeof(IStartup), sp => - { - var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>(); - var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName); - return new ConventionBasedStartup(methods); - }); + var hostingEnvironment = serviceProvider.GetRequiredService<IHostEnvironment>(); + var methods = StartupLoader.LoadMethods(serviceProvider, startupType, hostingEnvironment.EnvironmentName); + return new ConventionBasedStartup(methods); } } - catch (Exception ex) + } + catch (Exception ex) + { + var capture = ExceptionDispatchInfo.Capture(ex); + services.AddSingleton<IStartup>(_ => { - var capture = ExceptionDispatchInfo.Capture(ex); - services.AddSingleton<IStartup>(_ => - { - capture.Throw(); - return null; - }); - } + capture.Throw(); + return null; + }); } - - _configureServices?.Invoke(_context, services); - - return services; } private static void AddApplicationServices(IServiceCollection services, IServiceProvider hostingServiceProvider) diff --git a/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs index 1e5ddb6efc..1a67283754 100644 --- a/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs +++ b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs @@ -93,6 +93,7 @@ public static class WebHostBuilderExtensions /// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> /// <remarks>When using the il linker, all public methods of <typeparamref name="TStartup"/> are preserved. This should match the Startup type directly (and not a base type).</remarks> + [RequiresUnreferencedCode("Startup type created by factory can't be determined statically. Specify the startup type explicitly in configuration.")] public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TStartup>(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, TStartup> startupFactory) where TStartup : class { if (startupFactory == null) @@ -113,11 +114,14 @@ public static class WebHostBuilderExtensions return hostBuilder .ConfigureServices((context, services) => { - services.AddSingleton(typeof(IStartup), sp => + services.AddSingleton(typeof(IStartup), GetStartupInstance); + + [UnconditionalSuppressMessage("Trimmer", "IL2072", Justification = "Startup type created by factory can't be determined statically.")] + object GetStartupInstance(IServiceProvider serviceProvider) { var instance = startupFactory(context) ?? throw new InvalidOperationException("The specified factory returned null startup instance."); - var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>(); + var hostingEnvironment = serviceProvider.GetRequiredService<IHostEnvironment>(); // Check if the instance implements IStartup before wrapping if (instance is IStartup startup) @@ -125,8 +129,8 @@ public static class WebHostBuilderExtensions return startup; } - return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, instance.GetType(), hostingEnvironment.EnvironmentName, instance)); - }); + return new ConventionBasedStartup(StartupLoader.LoadMethods(serviceProvider, instance.GetType(), hostingEnvironment.EnvironmentName, instance)); + } }); } @@ -153,19 +157,21 @@ public static class WebHostBuilderExtensions hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName); + var state = new UseStartupState(startupType); + return hostBuilder .ConfigureServices(services => { - if (typeof(IStartup).IsAssignableFrom(startupType)) + if (typeof(IStartup).IsAssignableFrom(state.StartupType)) { - services.AddSingleton(typeof(IStartup), startupType); + services.AddSingleton(typeof(IStartup), state.StartupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>(); - return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); + return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, state.StartupType, hostingEnvironment.EnvironmentName)); }); } }); diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj index 6213aca587..d03f2d43cf 100644 --- a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj +++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj @@ -9,6 +9,7 @@ <PackageTags>aspnetcore</PackageTags> <IsPackable>false</IsPackable> <Nullable>enable</Nullable> + <Trimmable>true</Trimmable> </PropertyGroup> <ItemGroup> diff --git a/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj b/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj index 3617380371..8a853af7a4 100644 --- a/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj +++ b/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj @@ -8,6 +8,7 @@ <GenerateDocumentationFile>true</GenerateDocumentationFile> <PackageTags>aspnetcore</PackageTags> <Nullable>enable</Nullable> + <Trimmable>true</Trimmable> </PropertyGroup> <ItemGroup> diff --git a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs index 93b3b667d0..4c0ce8c3c7 100644 --- a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs +++ b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -16,6 +17,7 @@ namespace Microsoft.Extensions.StackTrace.Sources; internal class StackTraceHelper { + [UnconditionalSuppressMessage("Trimmer", "IL2026", Justification = "MethodInfo for a stack frame might be incomplete or removed. GetFrames does the best it can to provide frame details.")] public static IList<StackFrameInfo> GetFrames(Exception exception, out AggregateException? error) { if (exception == null) @@ -43,13 +45,20 @@ internal class StackTraceHelper var frame = stackFrames[i]; var method = frame.GetMethod(); + // It seems like a MethodInfo should always be available for methods in the stack, + // but double check we have one for trimmed apps. + if (method == null) + { + continue; + } + // Always show last stackFrame if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) { continue; } - var stackFrame = new StackFrameInfo(frame.GetFileLineNumber(), frame.GetFileName(), frame, GetMethodDisplayString(frame.GetMethod())); + var stackFrame = new StackFrameInfo(frame.GetFileLineNumber(), frame.GetFileName(), frame, GetMethodDisplayString(method)); frames.Add(stackFrame); } @@ -140,10 +149,8 @@ internal class StackTraceHelper return methodDisplayInfo; } - private static bool ShowInStackTrace(MethodBase? method) + private static bool ShowInStackTrace(MethodBase method) { - Debug.Assert(method != null); - // Don't show any methods marked with the StackTraceHiddenAttribute // https://github.com/dotnet/coreclr/pull/14652 if (HasStackTraceHiddenAttribute(method)) @@ -185,6 +192,7 @@ internal class StackTraceHelper return true; } + [UnconditionalSuppressMessage("Trimmer", "IL2075", Justification = "Unable to require a method has all information on it to resolve state machine.")] private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type? declaringType) { Debug.Assert(method != null); diff --git a/src/Shared/StaticWebAssets/ManifestStaticWebAssetFileProvider.cs b/src/Shared/StaticWebAssets/ManifestStaticWebAssetFileProvider.cs index 65cd301d67..45aa301118 100644 --- a/src/Shared/StaticWebAssets/ManifestStaticWebAssetFileProvider.cs +++ b/src/Shared/StaticWebAssets/ManifestStaticWebAssetFileProvider.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.StaticWebAssets; -internal sealed class ManifestStaticWebAssetFileProvider : IFileProvider +internal sealed partial class ManifestStaticWebAssetFileProvider : IFileProvider { private static readonly StringComparison _fsComparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : @@ -322,10 +322,23 @@ internal sealed class ManifestStaticWebAssetFileProvider : IFileProvider internal static StaticWebAssetManifest Parse(Stream manifest) { - return JsonSerializer.Deserialize<StaticWebAssetManifest>(manifest)!; + return JsonSerializer.Deserialize( + manifest, + SourceGenerationContext.DefaultWithConverter.StaticWebAssetManifest)!; } } + [JsonSourceGenerationOptions] + [JsonSerializable(typeof(StaticWebAssetManifest))] + [JsonSerializable(typeof(IDictionary<string, StaticWebAssetNode>))] + internal partial class SourceGenerationContext : JsonSerializerContext + { + public static readonly SourceGenerationContext DefaultWithConverter = new SourceGenerationContext(new JsonSerializerOptions + { + Converters = { new OSBasedCaseConverter() } + }); + } + internal sealed class StaticWebAssetNode { [JsonPropertyName("Asset")] @@ -366,7 +379,10 @@ internal sealed class ManifestStaticWebAssetFileProvider : IFileProvider { public override Dictionary<string, StaticWebAssetNode> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var parsed = JsonSerializer.Deserialize<IDictionary<string, StaticWebAssetNode>>(ref reader, options)!; + // Need to recursively deserialize `Dictionary<string, StaticWebAssetNode>` but can't deserialize + // that type directly because this converter will call into itself and stackoverflow. + // Workaround is to deserialize to IDictionary, and then perform custom convert logic on the result. + var parsed = JsonSerializer.Deserialize(ref reader, SourceGenerationContext.DefaultWithConverter.IDictionaryStringStaticWebAssetNode)!; var result = new Dictionary<string, StaticWebAssetNode>(StaticWebAssetManifest.PathComparer); MergeChildren(parsed, result); return result; @@ -422,7 +438,7 @@ internal sealed class ManifestStaticWebAssetFileProvider : IFileProvider public override void Write(Utf8JsonWriter writer, Dictionary<string, StaticWebAssetNode> value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, options); + throw new NotSupportedException(); } } } diff --git a/src/Tools/Tools.slnf b/src/Tools/Tools.slnf index 3db571352b..3b15716bb2 100644 --- a/src/Tools/Tools.slnf +++ b/src/Tools/Tools.slnf @@ -12,9 +12,11 @@ "src\\Components\\Web\\src\\Microsoft.AspNetCore.Components.Web.csproj", "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", + "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", + "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", "src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj", |