Skip to content

Commit 404d817

Browse files
authored
Dipping toes into linker friendliness (#24458)
- Annotated UseMiddleware and UseStartup to preserve constructors and public methods. - Annotated UseHub and MapHub preserve constructors and public methods. - Added a test to verify UseStartup and UseMiddleware works - The linker.xml preserves constructors all of the types that are registered in DI. This should be removed once we get the linker friendly DI changes.
1 parent dd25b5f commit 404d817

22 files changed

+405
-27
lines changed

src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7+
using System.Diagnostics.CodeAnalysis;
78
using System.Linq;
89
using System.Reflection;
910
using System.Runtime.ExceptionServices;
1011
using Microsoft.AspNetCore.Builder;
1112
using Microsoft.AspNetCore.Hosting.Builder;
13+
using Microsoft.AspNetCore.Hosting.Internal;
1214
using Microsoft.AspNetCore.Http;
1315
using Microsoft.Extensions.Configuration;
1416
using Microsoft.Extensions.DependencyInjection;
@@ -196,7 +198,7 @@ public IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, S
196198
return this;
197199
}
198200

199-
public IWebHostBuilder UseStartup(Type startupType)
201+
public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
200202
{
201203
// UseStartup can be called multiple times. Only run the last one.
202204
_startupObject = startupType;
@@ -213,7 +215,7 @@ public IWebHostBuilder UseStartup(Type startupType)
213215
return this;
214216
}
215217

216-
public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
218+
public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
217219
{
218220
// Clear the startup type
219221
_startupObject = startupFactory;
@@ -232,7 +234,7 @@ public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFac
232234
return this;
233235
}
234236

235-
private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null)
237+
private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null)
236238
{
237239
var webHostBuilderContext = GetWebHostBuilderContext(context);
238240
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];

src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics.CodeAnalysis;
56
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.AspNetCore.Hosting.Internal;
68
using Microsoft.Extensions.Configuration;
79
using Microsoft.Extensions.DependencyInjection;
810

@@ -71,12 +73,12 @@ public IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuild
7173
return _builder.Configure(configure);
7274
}
7375

74-
public IWebHostBuilder UseStartup(Type startupType)
76+
public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
7577
{
7678
return _builder.UseStartup(startupType);
7779
}
7880

79-
public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
81+
public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
8082
{
8183
return _builder.UseStartup(startupFactory);
8284
}

src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics.CodeAnalysis;
56
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.AspNetCore.Hosting.Internal;
68

79
namespace Microsoft.AspNetCore.Hosting
810
{
911
internal interface ISupportsStartup
1012
{
1113
IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure);
12-
IWebHostBuilder UseStartup(Type startupType);
13-
IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory);
14+
IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType);
15+
IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory);
1416
}
1517
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
namespace Microsoft.AspNetCore.Hosting.Internal
7+
{
8+
internal static class StartupLinkerOptions
9+
{
10+
// We're going to keep all public constructors and public methods on Startup classes
11+
public const DynamicallyAccessedMemberTypes Accessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;
12+
}
13+
}

src/Hosting/Hosting/src/Internal/StartupLoader.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Globalization;
78
using System.Linq;
89
using System.Reflection;
10+
using Microsoft.AspNetCore.Hosting.Internal;
911
using Microsoft.Extensions.DependencyInjection;
1012

1113
namespace Microsoft.AspNetCore.Hosting
@@ -37,7 +39,7 @@ internal class StartupLoader
3739
//
3840
// If the Startup class ConfigureServices returns an <see cref="IServiceProvider"/> and there is at least an <see cref="IStartupConfigureServicesFilter"/> registered we
3941
// throw as the filters can't be applied.
40-
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName, object instance = null)
42+
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName, object instance = null)
4143
{
4244
var configureMethod = FindConfigureDelegate(startupType, environmentName);
4345

@@ -272,31 +274,31 @@ public static Type FindStartupType(string startupAssemblyName, string environmen
272274
return type;
273275
}
274276

275-
internal static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
277+
internal static ConfigureBuilder FindConfigureDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
276278
{
277279
var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
278280
return new ConfigureBuilder(configureMethod);
279281
}
280282

281-
internal static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
283+
internal static ConfigureContainerBuilder FindConfigureContainerDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
282284
{
283285
var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
284286
return new ConfigureContainerBuilder(configureMethod);
285287
}
286288

287-
internal static bool HasConfigureServicesIServiceProviderDelegate(Type startupType, string environmentName)
289+
internal static bool HasConfigureServicesIServiceProviderDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
288290
{
289291
return null != FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false);
290292
}
291293

292-
internal static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
294+
internal static ConfigureServicesBuilder FindConfigureServicesDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
293295
{
294296
var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
295297
?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
296298
return new ConfigureServicesBuilder(servicesMethod);
297299
}
298300

299-
private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
301+
private static MethodInfo FindMethod([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
300302
{
301303
var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName);
302304
var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, "");

src/Hosting/Hosting/src/WebHostBuilderExtensions.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Reflection;
67
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Hosting.Internal;
79
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
810
using Microsoft.Extensions.Configuration;
911
using Microsoft.Extensions.DependencyInjection;
@@ -67,7 +69,8 @@ private static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Actio
6769
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
6870
/// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param>
6971
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
70-
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, object> startupFactory)
72+
/// <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>
73+
public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, TStartup> startupFactory) where TStartup : class
7174
{
7275
if (startupFactory == null)
7376
{
@@ -110,7 +113,7 @@ public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<
110113
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
111114
/// <param name="startupType">The <see cref="Type"/> to be used.</param>
112115
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
113-
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
116+
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
114117
{
115118
if (startupType == null)
116119
{
@@ -151,7 +154,7 @@ public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type
151154
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
152155
/// <typeparam name ="TStartup">The type containing the startup methods for the application.</typeparam>
153156
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
154-
public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
157+
public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)]TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
155158
{
156159
return hostBuilder.UseStartup(typeof(TStartup));
157160
}

src/Hosting/Hosting/test/Fakes/GenericWebHostBuilderWrapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public IWebHostBuilder UseStartup(Type startupType)
7474
return this;
7575
}
7676

77-
public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
77+
public IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
7878
{
7979
_builder.UseStartup(startupFactory);
8080
return this;

src/Hosting/Hosting/test/WebHostBuilderTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void UseStartupThrowsWhenFactoryIsNull(IWebHostBuilder builder)
8585
public void UseStartupThrowsWhenFactoryReturnsNull(IWebHostBuilder builder)
8686
{
8787
var server = new TestServer();
88-
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseServer(server).UseStartup(context => null).Build());
88+
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseServer(server).UseStartup<object>(context => null).Build());
8989
Assert.Equal("The specified factory returned null startup instance.", ex.Message);
9090
}
9191

@@ -96,7 +96,7 @@ public async Task MultipleUseStartupCallsLastWins(IWebHostBuilder builder)
9696
var server = new TestServer();
9797
var host = builder.UseServer(server)
9898
.UseStartup<StartupCtorThrows>()
99-
.UseStartup(context => throw new InvalidOperationException("This doesn't run"))
99+
.UseStartup<object>(context => throw new InvalidOperationException("This doesn't run"))
100100
.Configure(app =>
101101
{
102102
throw new InvalidOperationException("This doesn't run");

src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public virtual Task<PublishedApplication> Publish(DeploymentParameters deploymen
3838
// avoids triggering builds of dependencies of the test app which could cause issues like https://github.com/dotnet/arcade/issues/2941
3939
+ $" --no-dependencies"
4040
+ $" /p:TargetArchitecture={deploymentParameters.RuntimeArchitecture}"
41-
+ " --no-restore";
41+
+ (deploymentParameters.RestoreDependencies ? "" : " --no-restore");
4242

4343
if (deploymentParameters.ApplicationType == ApplicationType.Standalone)
4444
{

src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ public DeploymentParameters(DeploymentParameters parameters)
113113
/// </summary>
114114
public string ApplicationBaseUriHint { get; set; }
115115

116+
public bool RestoreDependencies { get; set; }
117+
116118
/// <summary>
117119
/// Scheme used by the deployed application if <see cref="ApplicationBaseUriHint"/> is empty.
118120
/// </summary>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Server.IntegrationTesting;
8+
using Microsoft.AspNetCore.Testing;
9+
using Microsoft.Extensions.Logging;
10+
using Xunit;
11+
12+
namespace Microsoft.AspNetCore.Hosting.FunctionalTests
13+
{
14+
public class LinkedApplicationTests : LoggedTest
15+
{
16+
[Fact]
17+
public async Task LinkedApplicationWorks()
18+
{
19+
using (StartLog(out var loggerFactory))
20+
{
21+
var logger = loggerFactory.CreateLogger("LinkedApplicationWorks");
22+
23+
// https://github.com/dotnet/aspnetcore/issues/8247
24+
#pragma warning disable 0618
25+
var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "testassets",
26+
"BasicLinkedApp");
27+
#pragma warning restore 0618
28+
29+
var deploymentParameters = new DeploymentParameters(
30+
applicationPath,
31+
ServerType.Kestrel,
32+
RuntimeFlavor.CoreClr,
33+
RuntimeArchitecture.x64)
34+
{
35+
TargetFramework = Tfm.Net50,
36+
RuntimeArchitecture = RuntimeArchitecture.x64,
37+
ApplicationType = ApplicationType.Standalone,
38+
PublishApplicationBeforeDeployment = true,
39+
RestoreDependencies = true,
40+
StatusMessagesEnabled = false
41+
};
42+
43+
using var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory);
44+
45+
var result = await deployer.DeployAsync();
46+
47+
// The app should have started up
48+
Assert.False(deployer.HostProcess.HasExited);
49+
50+
var response = await RetryHelper.RetryRequest(() => result.HttpClient.GetAsync("/"), logger, retryCount: 10);
51+
var body = await response.Content.ReadAsStringAsync();
52+
53+
Assert.Equal("Hello World", body);
54+
}
55+
}
56+
}
57+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<PublishTrimmed>true</PublishTrimmed>
7+
<TrimMode>link</TrimMode>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Reference Include="Microsoft.AspNetCore.Hosting" />
12+
<Reference Include="Microsoft.Extensions.Hosting" />
13+
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<TrimmerRootDescriptor Include="Linker.xml" />
18+
</ItemGroup>
19+
20+
<!-- Link all assemblies -->
21+
<Target Name="EnsureAllAssembliesAreLinked" BeforeTargets="PrepareForILLink">
22+
<ItemGroup>
23+
<ManagedAssemblyToLink>
24+
<TrimMode>link</TrimMode>
25+
</ManagedAssemblyToLink>
26+
<!-- Root this project assembly -->
27+
<TrimmerRootAssembly Include="$(TargetName)" />
28+
</ItemGroup>
29+
</Target>
30+
31+
</Project>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- See: https://github.com/mono/linker/blob/master/src/linker/README.md#syntax-of-xml-descriptor -->
3+
<linker>
4+
<assembly fullname="Microsoft.Extensions.Hosting">
5+
<type fullname="Microsoft.Extensions.Hosting.Internal.ApplicationLifetime" />
6+
<type fullname="Microsoft.Extensions.Hosting.Internal.ConsoleLifetime" />
7+
<type fullname="Microsoft.Extensions.Hosting.ConsoleLifetimeOptions" />
8+
<type fullname="Microsoft.Extensions.Hosting.Internal.Host" />
9+
<type fullname="Microsoft.Extensions.Hosting.HostOptions" />
10+
</assembly>
11+
<assembly fullname="Microsoft.Extensions.Logging">
12+
<type fullname="Microsoft.Extensions.Logging.LoggerFactory" />
13+
</assembly>
14+
<assembly fullname="Microsoft.Extensions.Logging.Configuration">
15+
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderConfigurationFactory" />
16+
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderConfigureOptions`2" />
17+
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderOptionsChangeTokenSource`2" />
18+
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderConfiguration`1" />
19+
</assembly>
20+
<assembly fullname="Microsoft.Extensions.Logging.Console" preserve="all" />
21+
<assembly fullname="Microsoft.AspNetCore.Hosting">
22+
<type fullname="Microsoft.AspNetCore.Hosting.GenericWebHostApplicationLifetime" />
23+
<type fullname="Microsoft.AspNetCore.Http.DefaultHttpContextFactory" />
24+
<type fullname="Microsoft.AspNetCore.Hosting.Builder.ApplicationBuilderFactory" />
25+
<type fullname="Microsoft.AspNetCore.Hosting.GenericWebHostService" />
26+
<type fullname="Microsoft.AspNetCore.Hosting.GenericWebHostServiceOptions" />
27+
</assembly>
28+
<assembly fullname="Microsoft.AspNetCore.Http">
29+
<type fullname="Microsoft.AspNetCore.Http.MiddlewareFactory" />
30+
</assembly>
31+
<assembly fullname="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets">
32+
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory" />
33+
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions" />
34+
</assembly>
35+
<assembly fullname="Microsoft.AspNetCore.Server.Kestrel.Core">
36+
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Core.Internal.KestrelServerOptionsSetup" />
37+
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer" />
38+
</assembly>
39+
<assembly fullname="Microsoft.Extensions.Options">
40+
<type fullname="Microsoft.Extensions.Options.OptionsCache`1" />
41+
<type fullname="Microsoft.Extensions.Options.OptionsFactory`1" />
42+
<type fullname="Microsoft.Extensions.Options.OptionsMonitor`1" />
43+
<type fullname="Microsoft.Extensions.Options.OptionsManager`1" />
44+
</assembly>
45+
</linker>

0 commit comments

Comments
 (0)