Skip to content

Dipping toes into linker friendliness #24458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
Expand Down Expand Up @@ -213,7 +214,7 @@ public IWebHostBuilder UseStartup(Type startupType)
return this;
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
public IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
{
// Clear the startup type
_startupObject = startupFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public IWebHostBuilder UseStartup(Type startupType)
return _builder.UseStartup(startupType);
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
public IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
{
return _builder.UseStartup(startupFactory);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ internal interface ISupportsStartup
{
IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure);
IWebHostBuilder UseStartup(Type startupType);
IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory);
IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory);
}
}
10 changes: 7 additions & 3 deletions src/Hosting/Hosting/src/WebHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
Expand All @@ -15,6 +16,9 @@ namespace Microsoft.AspNetCore.Hosting
{
public static class WebHostBuilderExtensions
{
// We're going to keep all public constructors and public methods on Startup classes
private const DynamicallyAccessedMemberTypes StartupAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;

/// <summary>
/// Specify the startup method to be used to configure the web application.
/// </summary>
Expand Down Expand Up @@ -67,7 +71,7 @@ private static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Actio
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, object> startupFactory)
public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, TStartup> startupFactory)
{
if (startupFactory == null)
{
Expand Down Expand Up @@ -110,7 +114,7 @@ public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="startupType">The <see cref="Type"/> to be used.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, [DynamicallyAccessedMembers(StartupAccessibility)] Type startupType)
{
if (startupType == null)
{
Expand Down Expand Up @@ -151,7 +155,7 @@ public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <typeparam name ="TStartup">The type containing the startup methods for the application.</typeparam>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(StartupAccessibility)]TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
{
return hostBuilder.UseStartup(typeof(TStartup));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public IWebHostBuilder UseStartup(Type startupType)
return this;
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
public IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
{
_builder.UseStartup(startupFactory);
return this;
Expand Down
4 changes: 2 additions & 2 deletions src/Hosting/Hosting/test/WebHostBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void UseStartupThrowsWhenFactoryIsNull(IWebHostBuilder builder)
public void UseStartupThrowsWhenFactoryReturnsNull(IWebHostBuilder builder)
{
var server = new TestServer();
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseServer(server).UseStartup(context => null).Build());
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseServer(server).UseStartup<object>(context => null).Build());
Assert.Equal("The specified factory returned null startup instance.", ex.Message);
}

Expand All @@ -96,7 +96,7 @@ public async Task MultipleUseStartupCallsLastWins(IWebHostBuilder builder)
var server = new TestServer();
var host = builder.UseServer(server)
.UseStartup<StartupCtorThrows>()
.UseStartup(context => throw new InvalidOperationException("This doesn't run"))
.UseStartup<object>(context => throw new InvalidOperationException("This doesn't run"))
.Configure(app =>
{
throw new InvalidOperationException("This doesn't run");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,14 +23,17 @@ public static class UseMiddlewareExtensions

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;

/// <summary>
/// Adds a middleware type to the application's request pipeline.
/// </summary>
/// <typeparam name="TMiddleware">The middleware type.</typeparam>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
public static IApplicationBuilder UseMiddleware<TMiddleware>(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);
}
Expand All @@ -41,7 +45,7 @@ public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBu
/// <param name="middleware">The middleware type.</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
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()))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// 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;

namespace Microsoft.AspNetCore.Connections
{
public static class ConnectionBuilderExtensions
{
public static IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler
public static IConnectionBuilder UseConnectionHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TConnectionHandler>(this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler
{
var handler = ActivatorUtilities.GetServiceOrCreateInstance<TConnectionHandler>(connectionBuilder.ApplicationServices);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

<Compile Include="$(SharedSourceRoot)ActivatorUtilities\*.cs" />
<Compile Include="$(SharedSourceRoot)ParameterDefaultValue\*.cs" />
<Compile Include="$(SharedSourceRoot)CodeAnalysis\*.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
Expand Down
92 changes: 92 additions & 0 deletions src/Shared/CodeAnalysis/DynamicallyAccessedMemberTypes.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Specifies the types of members that are dynamically accessed.
///
/// This enumeration has a <see cref="FlagsAttribute"/> attribute that allows a
/// bitwise combination of its member values.
/// </summary>
[Flags]
internal enum DynamicallyAccessedMemberTypes
{
/// <summary>
/// Specifies no members.
/// </summary>
None = 0,

/// <summary>
/// Specifies the default, parameterless public constructor.
/// </summary>
PublicParameterlessConstructor = 0x0001,

/// <summary>
/// Specifies all public constructors.
/// </summary>
PublicConstructors = 0x0002 | PublicParameterlessConstructor,

/// <summary>
/// Specifies all non-public constructors.
/// </summary>
NonPublicConstructors = 0x0004,

/// <summary>
/// Specifies all public methods.
/// </summary>
PublicMethods = 0x0008,

/// <summary>
/// Specifies all non-public methods.
/// </summary>
NonPublicMethods = 0x0010,

/// <summary>
/// Specifies all public fields.
/// </summary>
PublicFields = 0x0020,

/// <summary>
/// Specifies all non-public fields.
/// </summary>
NonPublicFields = 0x0040,

/// <summary>
/// Specifies all public nested types.
/// </summary>
PublicNestedTypes = 0x0080,

/// <summary>
/// Specifies all non-public nested types.
/// </summary>
NonPublicNestedTypes = 0x0100,

/// <summary>
/// Specifies all public properties.
/// </summary>
PublicProperties = 0x0200,

/// <summary>
/// Specifies all non-public properties.
/// </summary>
NonPublicProperties = 0x0400,

/// <summary>
/// Specifies all public events.
/// </summary>
PublicEvents = 0x0800,

/// <summary>
/// Specifies all non-public events.
/// </summary>
NonPublicEvents = 0x1000,

/// <summary>
/// Specifies all members.
/// </summary>
All = ~None
}
}
#endif
48 changes: 48 additions & 0 deletions src/Shared/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Indicates that certain members on a specified <see cref="Type"/> are accessed dynamically,
/// for example through <see cref="System.Reflection"/>.
/// </summary>
/// <remarks>
/// 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 <see cref="Type"/> or <see cref="string"/>.
///
/// When this attribute is applied to a location of type <see cref="string"/>, 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).
/// </remarks>
[AttributeUsage(
AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter |
AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method,
Inherited = false)]
internal sealed class DynamicallyAccessedMembersAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="DynamicallyAccessedMembersAttribute"/> class
/// with the specified member types.
/// </summary>
/// <param name="memberTypes">The types of members dynamically accessed.</param>
public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes)
{
MemberTypes = memberTypes;
}

/// <summary>
/// Gets the <see cref="DynamicallyAccessedMemberTypes"/> which specifies the type
/// of members dynamically accessed.
/// </summary>
public DynamicallyAccessedMemberTypes MemberTypes { get; }
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,13 +14,15 @@ namespace Microsoft.AspNetCore.SignalR
/// </summary>
public static class SignalRConnectionBuilderExtensions
{
private const DynamicallyAccessedMemberTypes HubAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;

/// <summary>
/// Configure the connection to host the specified <see cref="Hub"/> type.
/// </summary>
/// <typeparam name="THub">The <see cref="Hub"/> type to host on the connection.</typeparam>
/// <param name="connectionBuilder">The connection to configure.</param>
/// <returns>The same instance of the <see cref="IConnectionBuilder"/> for chaining.</returns>
public static IConnectionBuilder UseHub<THub>(this IConnectionBuilder connectionBuilder) where THub : Hub
public static IConnectionBuilder UseHub<[DynamicallyAccessedMembers(HubAccessibility)]THub>(this IConnectionBuilder connectionBuilder) where THub : Hub
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this should be done in gRPC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
var marker = connectionBuilder.ApplicationServices.GetService(typeof(SignalRCoreMarkerService));
if (marker == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,14 +12,16 @@ namespace Microsoft.AspNetCore.Builder
{
public static class HubEndpointRouteBuilderExtensions
{
private const DynamicallyAccessedMemberTypes HubAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;

/// <summary>
/// Maps incoming requests with the specified path to the specified <see cref="Hub"/> type.
/// </summary>
/// <typeparam name="THub">The <see cref="Hub"/> type to map requests to.</typeparam>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <returns>An <see cref="HubEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static HubEndpointConventionBuilder MapHub<THub>(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<THub>(pattern, configureOptions: null);
}
Expand All @@ -31,7 +34,7 @@ public static HubEndpointConventionBuilder MapHub<THub>(this IEndpointRouteBuild
/// <param name="pattern">The route pattern.</param>
/// <param name="configureOptions">A callback to configure dispatcher options.</param>
/// <returns>An <see cref="HubEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static HubEndpointConventionBuilder MapHub<THub>(this IEndpointRouteBuilder endpoints, string pattern, Action<HttpConnectionDispatcherOptions> configureOptions) where THub : Hub
public static HubEndpointConventionBuilder MapHub<[DynamicallyAccessedMembers(HubAccessibility)]THub>(this IEndpointRouteBuilder endpoints, string pattern, Action<HttpConnectionDispatcherOptions> configureOptions) where THub : Hub
{
var marker = endpoints.ServiceProvider.GetService<SignalRMarkerService>();

Expand Down