From 404d81767784552b0a148cb8c437332ebe726ae9 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 3 Aug 2020 06:54:03 -0700 Subject: [PATCH] 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. --- .../src/GenericHost/GenericWebHostBuilder.cs | 8 +- .../HostingStartupWebHostBuilder.cs | 6 +- .../src/GenericHost/ISupportsStartup.cs | 6 +- .../src/Internal/StartupLinkerOptions.cs | 13 +++ .../Hosting/src/Internal/StartupLoader.cs | 14 +-- .../Hosting/src/WebHostBuilderExtensions.cs | 9 +- .../Fakes/GenericWebHostBuilderWrapper.cs | 2 +- .../Hosting/test/WebHostBuilderTests.cs | 4 +- .../src/ApplicationPublisher.cs | 2 +- .../src/Common/DeploymentParameters.cs | 2 + .../FunctionalTests/LinkedApplicationTests.cs | 57 ++++++++++++ .../BasicLinkedApp/BasicLinkedApp.csproj | 31 +++++++ .../test/testassets/BasicLinkedApp/Linker.xml | 45 +++++++++ .../test/testassets/BasicLinkedApp/Program.cs | 38 ++++++++ .../test/testassets/BasicLinkedApp/Startup.cs | 29 ++++++ .../src/Extensions/UseMiddlewareExtensions.cs | 10 +- .../src/ConnectionBuilderExtensions.cs | 3 +- ...AspNetCore.Connections.Abstractions.csproj | 1 + .../DynamicallyAccessedMemberTypes.cs | 92 +++++++++++++++++++ .../DynamicallyAccessedMembersAttribute.cs | 48 ++++++++++ .../src/SignalRConnectionBuilderExtensions.cs | 5 +- .../src/HubEndpointRouteBuilderExtensions.cs | 7 +- 22 files changed, 405 insertions(+), 27 deletions(-) create mode 100644 src/Hosting/Hosting/src/Internal/StartupLinkerOptions.cs create mode 100644 src/Hosting/test/FunctionalTests/LinkedApplicationTests.cs create mode 100644 src/Hosting/test/testassets/BasicLinkedApp/BasicLinkedApp.csproj create mode 100644 src/Hosting/test/testassets/BasicLinkedApp/Linker.xml create mode 100644 src/Hosting/test/testassets/BasicLinkedApp/Program.cs create mode 100644 src/Hosting/test/testassets/BasicLinkedApp/Startup.cs create mode 100644 src/Shared/CodeAnalysis/DynamicallyAccessedMemberTypes.cs create mode 100644 src/Shared/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs index a9625a7907..d3b97ca8f1 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Builder; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -196,7 +198,7 @@ namespace Microsoft.AspNetCore.Hosting return this; } - public IWebHostBuilder UseStartup(Type startupType) + public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType) { // UseStartup can be called multiple times. Only run the last one. _startupObject = startupType; @@ -213,7 +215,7 @@ namespace Microsoft.AspNetCore.Hosting return this; } - public IWebHostBuilder UseStartup(Func startupFactory) + public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func startupFactory) { // Clear the startup type _startupObject = startupFactory; @@ -232,7 +234,7 @@ namespace Microsoft.AspNetCore.Hosting return this; } - private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null) + private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null) { var webHostBuilderContext = GetWebHostBuilderContext(context); var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; diff --git a/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs index f4034fc38b..ccffd884db 100644 --- a/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -71,12 +73,12 @@ namespace Microsoft.AspNetCore.Hosting return _builder.Configure(configure); } - public IWebHostBuilder UseStartup(Type startupType) + public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType) { return _builder.UseStartup(startupType); } - public IWebHostBuilder UseStartup(Func startupFactory) + public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func startupFactory) { return _builder.UseStartup(startupFactory); } diff --git a/src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs b/src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs index 998f73d06b..360c3da631 100644 --- a/src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs +++ b/src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs @@ -2,14 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Internal; namespace Microsoft.AspNetCore.Hosting { internal interface ISupportsStartup { IWebHostBuilder Configure(Action configure); - IWebHostBuilder UseStartup(Type startupType); - IWebHostBuilder UseStartup(Func startupFactory); + IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType); + IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func startupFactory); } } diff --git a/src/Hosting/Hosting/src/Internal/StartupLinkerOptions.cs b/src/Hosting/Hosting/src/Internal/StartupLinkerOptions.cs new file mode 100644 index 0000000000..44ce693c31 --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/StartupLinkerOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal static class StartupLinkerOptions + { + // We're going to keep all public constructors and public methods on Startup classes + public const DynamicallyAccessedMemberTypes Accessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods; + } +} diff --git a/src/Hosting/Hosting/src/Internal/StartupLoader.cs b/src/Hosting/Hosting/src/Internal/StartupLoader.cs index 8c80884b83..8e8f235eae 100644 --- a/src/Hosting/Hosting/src/Internal/StartupLoader.cs +++ b/src/Hosting/Hosting/src/Internal/StartupLoader.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Hosting @@ -37,7 +39,7 @@ namespace Microsoft.AspNetCore.Hosting // // If the Startup class ConfigureServices returns an and there is at least an registered we // throw as the filters can't be applied. - public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName, object instance = null) + public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName, object instance = null) { var configureMethod = FindConfigureDelegate(startupType, environmentName); @@ -272,31 +274,31 @@ namespace Microsoft.AspNetCore.Hosting return type; } - internal static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) + internal static ConfigureBuilder FindConfigureDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); return new ConfigureBuilder(configureMethod); } - internal static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) + internal static ConfigureContainerBuilder FindConfigureContainerDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false); return new ConfigureContainerBuilder(configureMethod); } - internal static bool HasConfigureServicesIServiceProviderDelegate(Type startupType, string environmentName) + internal static bool HasConfigureServicesIServiceProviderDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName) { return null != FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false); } - internal static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) + internal static ConfigureServicesBuilder FindConfigureServicesDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName) { var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); return new ConfigureServicesBuilder(servicesMethod); } - private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true) + private static MethodInfo FindMethod([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true) { var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName); var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, ""); diff --git a/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs index 5d2b7133cc..20151ebe9e 100644 --- a/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs +++ b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Hosting.StaticWebAssets; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -67,7 +69,8 @@ namespace Microsoft.AspNetCore.Hosting /// The to configure. /// A delegate that specifies a factory for the startup class. /// The . - public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func startupFactory) + /// When using the il linker, all public methods of are preserved. This should match the Startup type directly (and not a base type). + public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(this IWebHostBuilder hostBuilder, Func startupFactory) where TStartup : class { if (startupFactory == null) { @@ -110,7 +113,7 @@ namespace Microsoft.AspNetCore.Hosting /// The to configure. /// The to be used. /// The . - public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) + public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType) { if (startupType == null) { @@ -151,7 +154,7 @@ namespace Microsoft.AspNetCore.Hosting /// The to configure. /// The type containing the startup methods for the application. /// The . - public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder) where TStartup : class + public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)]TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class { return hostBuilder.UseStartup(typeof(TStartup)); } diff --git a/src/Hosting/Hosting/test/Fakes/GenericWebHostBuilderWrapper.cs b/src/Hosting/Hosting/test/Fakes/GenericWebHostBuilderWrapper.cs index e87be3a9ad..b0bf122d1b 100644 --- a/src/Hosting/Hosting/test/Fakes/GenericWebHostBuilderWrapper.cs +++ b/src/Hosting/Hosting/test/Fakes/GenericWebHostBuilderWrapper.cs @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests.Fakes return this; } - public IWebHostBuilder UseStartup(Func startupFactory) + public IWebHostBuilder UseStartup(Func startupFactory) { _builder.UseStartup(startupFactory); return this; diff --git a/src/Hosting/Hosting/test/WebHostBuilderTests.cs b/src/Hosting/Hosting/test/WebHostBuilderTests.cs index 81a8ab4941..d6b9a5f1a6 100644 --- a/src/Hosting/Hosting/test/WebHostBuilderTests.cs +++ b/src/Hosting/Hosting/test/WebHostBuilderTests.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Hosting public void UseStartupThrowsWhenFactoryReturnsNull(IWebHostBuilder builder) { var server = new TestServer(); - var ex = Assert.Throws(() => builder.UseServer(server).UseStartup(context => null).Build()); + var ex = Assert.Throws(() => builder.UseServer(server).UseStartup(context => null).Build()); Assert.Equal("The specified factory returned null startup instance.", ex.Message); } @@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Hosting var server = new TestServer(); var host = builder.UseServer(server) .UseStartup() - .UseStartup(context => throw new InvalidOperationException("This doesn't run")) + .UseStartup(context => throw new InvalidOperationException("This doesn't run")) .Configure(app => { throw new InvalidOperationException("This doesn't run"); diff --git a/src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs b/src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs index ba5c545317..5ef431c2ce 100644 --- a/src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs +++ b/src/Hosting/Server.IntegrationTesting/src/ApplicationPublisher.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting // avoids triggering builds of dependencies of the test app which could cause issues like https://github.com/dotnet/arcade/issues/2941 + $" --no-dependencies" + $" /p:TargetArchitecture={deploymentParameters.RuntimeArchitecture}" - + " --no-restore"; + + (deploymentParameters.RestoreDependencies ? "" : " --no-restore"); if (deploymentParameters.ApplicationType == ApplicationType.Standalone) { diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs index 1f7287d3a0..5002e8f530 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs +++ b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs @@ -113,6 +113,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting /// public string ApplicationBaseUriHint { get; set; } + public bool RestoreDependencies { get; set; } + /// /// Scheme used by the deployed application if is empty. /// diff --git a/src/Hosting/test/FunctionalTests/LinkedApplicationTests.cs b/src/Hosting/test/FunctionalTests/LinkedApplicationTests.cs new file mode 100644 index 0000000000..8bce9c2b42 --- /dev/null +++ b/src/Hosting/test/FunctionalTests/LinkedApplicationTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace Microsoft.AspNetCore.Hosting.FunctionalTests +{ + public class LinkedApplicationTests : LoggedTest + { + [Fact] + public async Task LinkedApplicationWorks() + { + using (StartLog(out var loggerFactory)) + { + var logger = loggerFactory.CreateLogger("LinkedApplicationWorks"); + + // https://github.com/dotnet/aspnetcore/issues/8247 +#pragma warning disable 0618 + var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "testassets", + "BasicLinkedApp"); +#pragma warning restore 0618 + + var deploymentParameters = new DeploymentParameters( + applicationPath, + ServerType.Kestrel, + RuntimeFlavor.CoreClr, + RuntimeArchitecture.x64) + { + TargetFramework = Tfm.Net50, + RuntimeArchitecture = RuntimeArchitecture.x64, + ApplicationType = ApplicationType.Standalone, + PublishApplicationBeforeDeployment = true, + RestoreDependencies = true, + StatusMessagesEnabled = false + }; + + using var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory); + + var result = await deployer.DeployAsync(); + + // The app should have started up + Assert.False(deployer.HostProcess.HasExited); + + var response = await RetryHelper.RetryRequest(() => result.HttpClient.GetAsync("/"), logger, retryCount: 10); + var body = await response.Content.ReadAsStringAsync(); + + Assert.Equal("Hello World", body); + } + } + } +} diff --git a/src/Hosting/test/testassets/BasicLinkedApp/BasicLinkedApp.csproj b/src/Hosting/test/testassets/BasicLinkedApp/BasicLinkedApp.csproj new file mode 100644 index 0000000000..baf5d0e7d0 --- /dev/null +++ b/src/Hosting/test/testassets/BasicLinkedApp/BasicLinkedApp.csproj @@ -0,0 +1,31 @@ + + + + $(DefaultNetCoreTargetFramework) + Exe + true + link + + + + + + + + + + + + + + + + + link + + + + + + + diff --git a/src/Hosting/test/testassets/BasicLinkedApp/Linker.xml b/src/Hosting/test/testassets/BasicLinkedApp/Linker.xml new file mode 100644 index 0000000000..f3757db941 --- /dev/null +++ b/src/Hosting/test/testassets/BasicLinkedApp/Linker.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hosting/test/testassets/BasicLinkedApp/Program.cs b/src/Hosting/test/testassets/BasicLinkedApp/Program.cs new file mode 100644 index 0000000000..d9dd267135 --- /dev/null +++ b/src/Hosting/test/testassets/BasicLinkedApp/Program.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; + +namespace BasicLinkedApp +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + // Do not change the signature of this method. It's used for tests. + private static IHostBuilder CreateWebHostBuilder(string[] args) + { + return new HostBuilder() + .ConfigureHostConfiguration(config => + { + config.AddCommandLine(args); + }) + .ConfigureLogging(logging => + { + logging.AddConsole(); + }) + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseKestrel().UseStartup(); + }); + } + } +} diff --git a/src/Hosting/test/testassets/BasicLinkedApp/Startup.cs b/src/Hosting/test/testassets/BasicLinkedApp/Startup.cs new file mode 100644 index 0000000000..d899b5352b --- /dev/null +++ b/src/Hosting/test/testassets/BasicLinkedApp/Startup.cs @@ -0,0 +1,29 @@ + +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; + +namespace BasicLinkedApp +{ + public class Startup + { + public void Configure(IApplicationBuilder app) + { + app.UseMiddleware(); + } + } + + public class HelloWorldMiddleware + { + public HelloWorldMiddleware(RequestDelegate next) + { + + } + + public Task InvokeAsync(HttpContext context) + { + return context.Response.WriteAsync("Hello World"); + } + } +} diff --git a/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs index f33707e671..164b601d57 100644 --- a/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs +++ b/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -22,6 +23,9 @@ namespace Microsoft.AspNetCore.Builder private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static)!; + // We're going to keep all public constructors and public methods on middleware + private const DynamicallyAccessedMemberTypes MiddlewareAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods; + /// /// Adds a middleware type to the application's request pipeline. /// @@ -29,7 +33,7 @@ namespace Microsoft.AspNetCore.Builder /// The instance. /// The arguments to pass to the middleware type instance's constructor. /// The instance. - public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, params object[] args) + public static IApplicationBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)]TMiddleware>(this IApplicationBuilder app, params object[] args) { return app.UseMiddleware(typeof(TMiddleware), args); } @@ -41,7 +45,7 @@ namespace Microsoft.AspNetCore.Builder /// The middleware type. /// The arguments to pass to the middleware type instance's constructor. /// The instance. - public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args) + public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object[] args) { if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())) { @@ -110,7 +114,7 @@ namespace Microsoft.AspNetCore.Builder }); } - private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType) + private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type middlewareType) { return app.Use(next => { diff --git a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs index 55b0311eb9..937f36b574 100644 --- a/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs +++ b/src/Servers/Connections.Abstractions/src/ConnectionBuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.Extensions.Internal; @@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Connections { public static class ConnectionBuilderExtensions { - public static IConnectionBuilder UseConnectionHandler(this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler + public static IConnectionBuilder UseConnectionHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TConnectionHandler>(this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler { var handler = ActivatorUtilities.GetServiceOrCreateInstance(connectionBuilder.ApplicationServices); 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 fcea395964..e83b0142a0 100644 --- a/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj +++ b/src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Shared/CodeAnalysis/DynamicallyAccessedMemberTypes.cs b/src/Shared/CodeAnalysis/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 0000000000..2edc474a0b --- /dev/null +++ b/src/Shared/CodeAnalysis/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,92 @@ +#if !NET5_0 +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all members. + /// + All = ~None + } +} +#endif \ No newline at end of file diff --git a/src/Shared/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs b/src/Shared/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs new file mode 100644 index 0000000000..c0ae1c6862 --- /dev/null +++ b/src/Shared/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs @@ -0,0 +1,48 @@ +#if !NET5_0 +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that certain members on a specified are accessed dynamically, + /// for example through . + /// + /// + /// This allows tools to understand which members are being accessed during the execution + /// of a program. + /// + /// This attribute is valid on members whose type is or . + /// + /// When this attribute is applied to a location of type , the assumption is + /// that the string represents a fully qualified type name. + /// + /// If the attribute is applied to a method it's treated as a special case and it implies + /// the attribute should be applied to the "this" parameter of the method. As such the attribute + /// should only be used on instance methods of types assignable to System.Type (or string, but no methods + /// will use it there). + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } +} +#endif \ No newline at end of file diff --git a/src/SignalR/server/Core/src/SignalRConnectionBuilderExtensions.cs b/src/SignalR/server/Core/src/SignalRConnectionBuilderExtensions.cs index 58888470cf..d06baed604 100644 --- a/src/SignalR/server/Core/src/SignalRConnectionBuilderExtensions.cs +++ b/src/SignalR/server/Core/src/SignalRConnectionBuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.Extensions.DependencyInjection; @@ -13,13 +14,15 @@ namespace Microsoft.AspNetCore.SignalR /// public static class SignalRConnectionBuilderExtensions { + private const DynamicallyAccessedMemberTypes HubAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods; + /// /// Configure the connection to host the specified type. /// /// The type to host on the connection. /// The connection to configure. /// The same instance of the for chaining. - public static IConnectionBuilder UseHub(this IConnectionBuilder connectionBuilder) where THub : Hub + public static IConnectionBuilder UseHub<[DynamicallyAccessedMembers(HubAccessibility)]THub>(this IConnectionBuilder connectionBuilder) where THub : Hub { var marker = connectionBuilder.ApplicationServices.GetService(typeof(SignalRCoreMarkerService)); if (marker == null) diff --git a/src/SignalR/server/SignalR/src/HubEndpointRouteBuilderExtensions.cs b/src/SignalR/server/SignalR/src/HubEndpointRouteBuilderExtensions.cs index 553ea3f8ce..3ac90b36f0 100644 --- a/src/SignalR/server/SignalR/src/HubEndpointRouteBuilderExtensions.cs +++ b/src/SignalR/server/SignalR/src/HubEndpointRouteBuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.SignalR; @@ -11,6 +12,8 @@ namespace Microsoft.AspNetCore.Builder { public static class HubEndpointRouteBuilderExtensions { + private const DynamicallyAccessedMemberTypes HubAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods; + /// /// Maps incoming requests with the specified path to the specified type. /// @@ -18,7 +21,7 @@ namespace Microsoft.AspNetCore.Builder /// The to add the route to. /// The route pattern. /// An for endpoints associated with the connections. - public static HubEndpointConventionBuilder MapHub(this IEndpointRouteBuilder endpoints, string pattern) where THub : Hub + public static HubEndpointConventionBuilder MapHub<[DynamicallyAccessedMembers(HubAccessibility)]THub>(this IEndpointRouteBuilder endpoints, string pattern) where THub : Hub { return endpoints.MapHub(pattern, configureOptions: null); } @@ -31,7 +34,7 @@ namespace Microsoft.AspNetCore.Builder /// The route pattern. /// A callback to configure dispatcher options. /// An for endpoints associated with the connections. - public static HubEndpointConventionBuilder MapHub(this IEndpointRouteBuilder endpoints, string pattern, Action configureOptions) where THub : Hub + public static HubEndpointConventionBuilder MapHub<[DynamicallyAccessedMembers(HubAccessibility)]THub>(this IEndpointRouteBuilder endpoints, string pattern, Action configureOptions) where THub : Hub { var marker = endpoints.ServiceProvider.GetService();