diff --git a/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs b/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs index 2c689c697..0f9b8852d 100644 --- a/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs +++ b/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs @@ -58,12 +58,14 @@ public static IGrpcServerBuilder AddGrpc(this IServiceCollection services) { ArgumentNullThrowHelper.ThrowIfNull(services); - services.AddRouting(options => - { - // Unimplemented constraint is added to the route as an inline constraint to avoid RoutePatternFactory.Parse overload that includes parameter policies. That overload infers strings as regex constraints, which brings in - // the regex engine when publishing trimmed or AOT apps. This change reduces Native AOT gRPC server app size by about 1 MB. - AddParameterPolicy(options, GrpcServerConstants.GrpcUnimplementedConstraintPrefix); - }); +#if NET8_0_OR_GREATER + // Prefer AddRoutingCore when available. + // AddRoutingCore doesn't register a regex constraint and produces smaller result from trimming. + services.AddRoutingCore(); + services.Configure(ConfigureRouting); +#else + services.AddRouting(ConfigureRouting); +#endif services.AddOptions(); services.TryAddSingleton(); services.TryAddSingleton(typeof(ServerCallHandlerFactory<>)); @@ -78,6 +80,13 @@ public static IGrpcServerBuilder AddGrpc(this IServiceCollection services) return new GrpcServerBuilder(services); + static void ConfigureRouting(RouteOptions options) + { + // Unimplemented constraint is added to the route as an inline constraint to avoid RoutePatternFactory.Parse overload that includes parameter policies. That overload infers strings as regex constraints, which brings in + // the regex engine when publishing trimmed or AOT apps. This change reduces Native AOT gRPC server app size by about 1 MB. + AddParameterPolicy(options, GrpcServerConstants.GrpcUnimplementedConstraintPrefix); + } + // This ensures the policy's constructors are preserved in .NET 6 with trimming. Remove when .NET 6 is no longer supported. static void AddParameterPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(RouteOptions options, string name) where T : IParameterPolicy diff --git a/testassets/LinkerTestsClient/LinkerTestsClient.csproj b/testassets/LinkerTestsClient/LinkerTestsClient.csproj index 4834024ce..86d03f466 100644 --- a/testassets/LinkerTestsClient/LinkerTestsClient.csproj +++ b/testassets/LinkerTestsClient/LinkerTestsClient.csproj @@ -5,6 +5,9 @@ Exe true $(AppPublishAot) + + $(GenerateAotDiaganostics) + $(GenerateAotDiaganostics) diff --git a/testassets/LinkerTestsClient/Program.cs b/testassets/LinkerTestsClient/Program.cs index af8218a31..c532c9643 100644 --- a/testassets/LinkerTestsClient/Program.cs +++ b/testassets/LinkerTestsClient/Program.cs @@ -22,72 +22,65 @@ using Microsoft.Extensions.DependencyInjection; using Unimplemented; -namespace Client; - // This app tests clients created directly from channel and clients created from factory. // Because of the vagaries of trimming, there is a small chance that testing both in the same app could // cause them to work when alone they might fail. Consider splitting into different client apps. -public class Program + +try { - static async Task Main(string[] args) + if (args.Length != 1 || !int.TryParse(args[0], out var port)) { - try - { - if (args.Length != 1 || !int.TryParse(args[0], out var port)) - { - throw new Exception("Port must be passed as an argument."); - } + throw new Exception("Port must be passed as an argument."); + } - var address = new Uri($"http://localhost:{port}"); + var address = new Uri($"http://localhost:{port}"); - // Basic channel - using var channel = GrpcChannel.ForAddress(address); - await CallGreeter(new Greeter.GreeterClient(channel)); - await CallUnimplemented(new UnimplementedService.UnimplementedServiceClient(channel)); + // Basic channel + using var channel = GrpcChannel.ForAddress(address); + await CallGreeter(new Greeter.GreeterClient(channel)); + await CallUnimplemented(new UnimplementedService.UnimplementedServiceClient(channel)); - // Client factory - var services = new ServiceCollection(); - services.AddGrpcClient(op => - { - op.Address = address; - }); - services.AddGrpcClient(op => - { - op.Address = address; - }); - var serviceProvider = services.BuildServiceProvider(); - - await CallGreeter(serviceProvider.GetRequiredService()); - await CallUnimplemented(serviceProvider.GetRequiredService()); + // Client factory + var services = new ServiceCollection(); + services.AddGrpcClient(op => + { + op.Address = address; + }); + services.AddGrpcClient(op => + { + op.Address = address; + }); + var serviceProvider = services.BuildServiceProvider(); - Console.WriteLine("Shutting down"); - return 0; - } - catch (Exception ex) - { - Console.Error.WriteLine(ex.ToString()); - return 1; - } - } + await CallGreeter(serviceProvider.GetRequiredService()); + await CallUnimplemented(serviceProvider.GetRequiredService()); - private static async Task CallGreeter(Greeter.GreeterClient client) + Console.WriteLine("Shutting down"); + return 0; +} +catch (Exception ex) +{ + Console.Error.WriteLine(ex.ToString()); + return 1; +} + +static async Task CallGreeter(Greeter.GreeterClient client) +{ + var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" }); + Console.WriteLine("Greeting: " + reply.Message); +} + +static async Task CallUnimplemented(UnimplementedService.UnimplementedServiceClient client) +{ + var reply = client.DuplexData(); + + try { - var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" }); - Console.WriteLine("Greeting: " + reply.Message); + await reply.ResponseStream.MoveNext(); + throw new Exception("Expected error status."); } - - private static async Task CallUnimplemented(UnimplementedService.UnimplementedServiceClient client) + catch (RpcException ex) when (ex.StatusCode == StatusCode.Unimplemented) { - var reply = client.DuplexData(); - - try - { - await reply.ResponseStream.MoveNext(); - throw new Exception("Expected error status."); - } - catch (RpcException ex) when (ex.StatusCode == StatusCode.Unimplemented) - { - Console.WriteLine("Unimplemented status correctly returned."); - } + Console.WriteLine("Unimplemented status correctly returned."); } } diff --git a/testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj b/testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj index 5d4cc37f0..c64180f3f 100644 --- a/testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj +++ b/testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj @@ -11,6 +11,9 @@ --> full false + + $(GenerateAotDiaganostics) + $(GenerateAotDiaganostics) diff --git a/testassets/LinkerTestsWebsite/Program.cs b/testassets/LinkerTestsWebsite/Program.cs index 8cb4bde69..eb0c95644 100644 --- a/testassets/LinkerTestsWebsite/Program.cs +++ b/testassets/LinkerTestsWebsite/Program.cs @@ -17,31 +17,23 @@ #endregion using Microsoft.AspNetCore.Server.Kestrel.Core; +using Server; -namespace Server; - -public class Program +var builder = WebApplication.CreateSlimBuilder(args); +builder.Logging.SetMinimumLevel(LogLevel.Trace); +builder.WebHost.ConfigureKestrel(options => { - public static void Main(string[] args) + options.ListenAnyIP(0, listenOptions => { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - webBuilder.ConfigureKestrel(options => - { - options.ListenAnyIP(0, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http2; - }); - }); - }) - .ConfigureLogging(logging => - { - logging.SetMinimumLevel(LogLevel.Trace); - }); -} + listenOptions.Protocols = HttpProtocols.Http2; + }); +}); + +builder.Services.AddGrpc(); + +var app = builder.Build(); + +app.MapGrpcService(); + +app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down.")); +app.Run(); diff --git a/testassets/LinkerTestsWebsite/Startup.cs b/testassets/LinkerTestsWebsite/Startup.cs deleted file mode 100644 index 9169811f3..000000000 --- a/testassets/LinkerTestsWebsite/Startup.cs +++ /dev/null @@ -1,44 +0,0 @@ -#region Copyright notice and license - -// Copyright 2019 The gRPC Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#endregion - -using Grpc.AspNetCore.Server; - -namespace Server; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddGrpc(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - }); - } -}