I have a Web API written with ASP.NET Core 8.0, and it’s configured to work with Http1 and Http2:
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1AndHttp2"
}
}
My problem is that I cannot do any gRPC calls to it (normal HTTP ones do work), and I have no clue how to address it (“connection refused” error).
The reason for HTTP2 is to be able to do gRPC calls, I am using the protobuf-net
libraries, and everything works fine, whenever I run my API, I get a couple of launch urls (http and https) and both HTTP and gRPC calls work.
My main issue is that I cannot configure my Integration Test Fixture to make it work.
First of all let me show you my Program.cs
:
var builder = WebApplication.CreateBuilder(args);
ServiceRegistration<AppSettings>.ConfigureServices(builder, "AdcTrains", "adc-trains",
containerBuilderAdditionalActions:
containerBuilder =>
{
},
additionalMiddlewareActions: webApp =>
{
if (webApp.Environment.IsDevelopment())
{
webApp.MapCodeFirstGrpcReflectionService();
}
webApp.MapGrpcService<TrainGrpcService>();
},
additionalPostBuildBehavior: async webApp =>
{
// write unit tests / integration tests rather than this
var tenantIdString = "19d74b3c-2955-4667-9bc5-15d67212469e";
var multitenantService = webApp.Services.GetRequiredService<MultitenantContainer>();
var tenantScope = multitenantService.GetTenantScope(tenantIdString);
var resolverService = tenantScope.Resolve<IGrpcResolverService>();
var trainGrpcService = resolverService.ResolveService<ITrainGrpcService>(Guid.Parse(tenantIdString));
});
/// <summary>
/// Not to be removed, used for the testing project
/// </summary>
public partial class Program
{ }
It’s pretty blank, the reason is that I use a base library written by me that enables various features, like gRPC, Redis, RabbitMQ, etc.
Within it I enable my gRPC code first
// this is actually exposing real grpc endpoints
services.AddCodeFirstGrpcReflection();
services.AddCodeFirstGrpc(config =>
{
config.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal;
});
I’ve posted the upper excerpt just to explain how my API works and how I resolve my grpc services (just to see they init properly since Integration Testing doesn’t work), now back to the integration test project.
This is my whole WebApplicationFactory
, written according to the documentation
/// <summary>
/// This is the base fixture for all the integration tests
/// https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0
/// </summary>
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram>, IAsyncLifetime where TProgram : class
{
protected const string TenantId = "19d74b3c-2955-4667-9bc5-15d67212469e";
// this will be initialized in InitializeAsync
public HttpClient HttpClient = default!;
public HubConnection WebHubConnection { get; set; }
public HubConnection RequestHubConnection { get; set; }
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Test");
builder
//.UseTestServer()
.ConfigureServices(services =>
{
services.AddCodeFirstGrpcReflection();
services.AddCodeFirstGrpc(config =>
{
config.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal;
config.EnableDetailedErrors = true;
});
})
.UseUrls("https://localhost:7034")
.ConfigureKestrel(k =>
{
k.ConfigureEndpointDefaults(lo => lo.Protocols = HttpProtocols.Http1AndHttp2);
k.ListenAnyIP(7034, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
})
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<TrainGrpcService>();
});
});
}
#region IAsyncLifetime
public async Task InitializeAsync()
{
HttpClient = CreateClient();
var configuration = Services.GetRequiredService<IConfiguration>();
var testConfiguration = configuration.GetSection("TestConfiguration");
var httpClient = new HttpClient();
var tokenResponse = await httpClient.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = testConfiguration.GetValue<string>("Ids"),
ClientId = testConfiguration.GetValue<string>("ClientId")!,
Scope = testConfiguration.GetValue<string>("Scope"),
UserName = testConfiguration.GetValue<string>("UserName")!,
Password = testConfiguration.GetValue<string>("Password")!
});
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);
HttpClient.DefaultRequestHeaders.Add("tenantId", TenantId);
// SignalR hookup
var hubConnectionService = new HubConnectionService(configuration);
var cancellationTokenSource = new CancellationTokenSource(15000);
// Front-end conn
WebHubConnection = await hubConnectionService.HubInitialize("webNotifications", tokenResponse.AccessToken, cancellationTokenSource.Token);
// Simulating the OBS system, basically in front-end == obs just so we can test easily
RequestHubConnection = await hubConnectionService.HubInitialize("trainRequest", tokenResponse.AccessToken, cancellationTokenSource.Token);
if (WebHubConnection.ConnectionId is null)
throw new Exception("Error at connecting to SignalR");
Log.Information("Hub connectionId: {hubConnectionId}", WebHubConnection.ConnectionId);
}
/// <summary>
/// Logout
/// </summary>
/// <returns></returns>
public async Task DisposeAsync()
{
var configuration = Services.GetRequiredService<IConfiguration>();
var testConfiguration = configuration.GetSection("TestConfiguration");
await HttpClient.RevokeTokenAsync(new TokenRevocationRequest
{
Token = HttpClient.DefaultRequestHeaders.Authorization!.Parameter,
Address = testConfiguration.GetValue<string>("Ids")
});
}
#endregion
}
And finally the test fixture:
public class TrainGrpcConfigFixture : IClassFixture<CustomWebApplicationFactory<Program>>
{
private readonly CustomWebApplicationFactory<Program> _factory;
public TrainGrpcConfigFixture(CustomWebApplicationFactory<Program> factory)
=> _factory = factory;
[Fact]
public async Task Get_NvrSet_Response_ExpectData()
{
// Arrange
var grpcResolver = _factory.Services.GetService(typeof(IGrpcResolverService)) as IGrpcResolverService;
var trainGrpcService = grpcResolver.ResolveService<ITrainGrpcService>();
var response = await trainGrpcService.GetNvrSetResponseAsync(new TrainConfig.Grpc.Models.Dtos.NvrSet.Requests.NvrSetRequest() { TrainId = 8 });
}
}
Whenever it attempts to connect it fails with connection refused…
So my guess, is that for some reason my Web API doesn’t start with a specific port as I’ve attempted to (7034) and it just plainly rejects me.
I have exhausted all my attempts before writing here, I did find some samples but they are pretty old and seem to be from around 2019~, tried including them here but they fail…. so I’ve just stuck with the official doc for .NET 8 which doesn’t cover much related go gRPC.
5