Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
21 changes: 15 additions & 6 deletions src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GrpcUnimplementedConstraint>(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<RouteOptions>(ConfigureRouting);
#else
services.AddRouting(ConfigureRouting);
#endif
services.AddOptions();
services.TryAddSingleton<GrpcMarkerService>();
services.TryAddSingleton(typeof(ServerCallHandlerFactory<>));
Expand All @@ -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<GrpcUnimplementedConstraint>(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
Expand Down
3 changes: 3 additions & 0 deletions testassets/LinkerTestsClient/LinkerTestsClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<OutputType>Exe</OutputType>
<PublishTrimmed>true</PublishTrimmed>
<PublishAot>$(AppPublishAot)</PublishAot>

<IlcGenerateMstatFile>$(GenerateAotDiaganostics)</IlcGenerateMstatFile>
<IlcGenerateDgmlFile>$(GenerateAotDiaganostics)</IlcGenerateDgmlFile>
</PropertyGroup>

<ItemGroup>
Expand Down
101 changes: 47 additions & 54 deletions testassets/LinkerTestsClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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<Greeter.GreeterClient>(op =>
{
op.Address = address;
});
services.AddGrpcClient<UnimplementedService.UnimplementedServiceClient>(op =>
{
op.Address = address;
});
var serviceProvider = services.BuildServiceProvider();

await CallGreeter(serviceProvider.GetRequiredService<Greeter.GreeterClient>());
await CallUnimplemented(serviceProvider.GetRequiredService<UnimplementedService.UnimplementedServiceClient>());
// Client factory
var services = new ServiceCollection();
services.AddGrpcClient<Greeter.GreeterClient>(op =>
{
op.Address = address;
});
services.AddGrpcClient<UnimplementedService.UnimplementedServiceClient>(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<Greeter.GreeterClient>());
await CallUnimplemented(serviceProvider.GetRequiredService<UnimplementedService.UnimplementedServiceClient>());

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.");
}
}
3 changes: 3 additions & 0 deletions testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
-->
<TrimMode>full</TrimMode>
<TrimmerSingleWarn>false</TrimmerSingleWarn>

<IlcGenerateMstatFile>$(GenerateAotDiaganostics)</IlcGenerateMstatFile>
<IlcGenerateDgmlFile>$(GenerateAotDiaganostics)</IlcGenerateDgmlFile>
</PropertyGroup>

<ItemGroup>
Expand Down
42 changes: 17 additions & 25 deletions testassets/LinkerTestsWebsite/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Startup>();
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<GreeterService>();

app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down."));
app.Run();
44 changes: 0 additions & 44 deletions testassets/LinkerTestsWebsite/Startup.cs

This file was deleted.