Calling another controller on same server using HttpClient fails when using TestServer

136 views Asked by At

I have a core Server project that uses:

services.AddControllers()
    .AddApplicationPart(gatewayAssembly)
    .AddApplicationPart(identitiesMicroserviceAssembly)
    .AddApplicationPart(exceptionsMicroserviceAssembly)
    .etc ...;

The controllers in gatewayAssembly use an HttpClient to communicate to controllers in microservice assemblies.

When creating the HttpClient in gatewayAssembly controller, I use an entry in appsettings.json to define the base Uri for each microservice. When running the project in development, that looks like:

  "MicroserviceUris": {
    "Identities": "https://localhost:7138",
    "Exceptions": "https://localhost:7138",
    "Entitlements": "https://localhost:7138",
    "Organisations": "https://localhost:7138",
    "Catalogs": "https://localhost:7138",
    "Taxes":  "https://localhost:7138",
    "Warehouse": "https://localhost:7138"
  },

The idea is that if I move a microservice to another server at some point then I just need to update the records in the above appsettings.json and I'm good to go.

When making a call from Postman, all works as planned. The gateway controller happily communicates with the microservice controller using HttpClient.

I'm now looking to build some integration tests using TestServer. In this case my integrationtestappsettings.json looks like:

  "MicroserviceUris": {
    "Identities": "http://localhost",
    "Exceptions": "http://localhost",
    "Entitlements": "http://localhost",
    "Organisations": "http://localhost",
    "Catalogs": "http://localhost",
    "Taxes": "http://localhost",
    "Warehouse": "http://localhost"
  },

From my test project I can call my gateway route fine. However, when that gateway controller tries to communicate with a microservice using base Uri from above I get a 404.

I wrote a test to communicate with the microservice directly using http://localhost/api/identities/users and it's there.

So, the problem appears to be with the HttpClient created by gateway controller on the Test Server as all routes appear to be loaded correctly.

The logs below show the 404 on the second hop, but as mentioned above I am able to hit that route if I send a request to that directly from the test project.

    18:27:17.499info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      => RequestPath:/api/uc/useraccountmanagement/registernewuser RequestId:0HMM1MRES2K3M
      Request starting HTTP/1.1 POST http://localhost/api/uc/useraccountmanagement/registernewuser application/json;+charset=utf-8 -
18:27:17.531info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      => RequestPath:/api/uc/useraccountmanagement/registernewuser RequestId:0HMM1MRES2K3M
      Executing endpoint 'Cosmos.Application.UseCases.UserAccountManagement.Controllers.RegisterNewUser.RegisterNewUserController.PostAsync (Cosmos.Application.UseCases.UserAccountManagement.Controllers)'
18:27:17.545info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
      => RequestPath:/api/uc/useraccountmanagement/registernewuser RequestId:0HMM1MRES2K3M => Cosmos.Application.UseCases.UserAccountManagement.Controllers.RegisterNewUser.RegisterNewUserController.PostAsync (Cosmos.Application.UseCases.UserAccountManagement.Controllers)
      Route matched with {action = "Post", controller = "RegisterNewUser"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] PostAsync(Cosmos.Application.UseCases.UserAccountManagement.Models.RegisterNewUser.RegisterNewUserModel, System.Collections.Generic.IEnumerable`1[Microsoft.AspNetCore.Routing.EndpointDataSource]) on controller Cosmos.Application.UseCases.UserAccountManagement.Controllers.RegisterNewUser.RegisterNewUserController (Cosmos.Application.UseCases.UserAccountManagement.Controllers).
18:27:17.572info: System.Net.Http.HttpClient.IdentitiesProxy.LogicalHandler[100]
      => RequestPath:/api/uc/useraccountmanagement/registernewuser RequestId:0HMM1MRES2K3M => Cosmos.Application.UseCases.UserAccountManagement.Controllers.RegisterNewUser.RegisterNewUserController.PostAsync (Cosmos.Application.UseCases.UserAccountManagement.Controllers) => HTTP POST http://localhost/api/identities/users
      Start processing HTTP request POST http://localhost/api/identities/users
18:27:17.573info: System.Net.Http.HttpClient.IdentitiesProxy.ClientHandler[100]
      => RequestPath:/api/uc/useraccountmanagement/registernewuser RequestId:0HMM1MRES2K3M => Cosmos.Application.UseCases.UserAccountManagement.Controllers.RegisterNewUser.RegisterNewUserController.PostAsync (Cosmos.Application.UseCases.UserAccountManagement.Controllers) => HTTP POST http://localhost/api/identities/users
      Sending HTTP request POST http://localhost/api/identities/users
18:27:17.612info: System.Net.Http.HttpClient.IdentitiesProxy.ClientHandler[101]
      => RequestPath:/api/uc/useraccountmanagement/registernewuser RequestId:0HMM1MRES2K3M => Cosmos.Application.UseCases.UserAccountManagement.Controllers.RegisterNewUser.RegisterNewUserController.PostAsync (Cosmos.Application.UseCases.UserAccountManagement.Controllers) => HTTP POST http://localhost/api/identities/users
      Received HTTP response headers after 37.8005ms - 404
18:27:17.612info: System.Net.Http.HttpClient.IdentitiesProxy.LogicalHandler[101]
      => RequestPath:/api/uc/useraccountmanagement/registernewuser RequestId:0HMM1MRES2K3M => Cosmos.Application.UseCases.UserAccountManagement.Controllers.RegisterNewUser.RegisterNewUserController.PostAsync (Cosmos.Application.UseCases.UserAccountManagement.Controllers) => HTTP POST http://localhost/api/identities/users
      End processing HTTP request after 42.4879ms - 404
1

There are 1 answers

0
Neil W On

As suspected, the issue was with the HttpClient created by the controller on the Test Server.

Specifically, it appears that you can only send an HttpRequest to TestServer when using a client created by that TestServer (or just the HttpMessageHandler).

As I wanted to send an HttpRequest FROM the TestServer to another route on the same TestServer, the client created in the first controller was not the one provided by TestServer, so cannot be used to send a request to another route on the TestServer.

The solution was to create multiple TestServers in the test project (one with the gateway controllers, and one with the microservice controllers). I had to split my startup code on the server into separate chunks (one chunk for those elements relating to gateway controllers and another for the microservice controllers). In that way, the test project could call the relevant startup code for each of the TestServers.

Then, when creating the TestServer for the microservices, I call:

TestServer microserviceTestService = new(builder);
HttpMessageHandler microserviceHandler = microserviceTestServer.CreateHandler();

to get a HttpMessageHandler for that TestServer.

Then, when registering HttpClient for the ApiGateway TestServer use

services.AddHttpClient<MyTypedClient>(client =>
{
    client.BaseAddress = gatewayTestServer.BaseAddress;
})
.ConfigurePrimaryHttpMessageHandler(microserviceHandler);

This means that when Gateway TestServer is using MyTypedClient to talk to Microservices TestServer the HttpClient is configured with the Message Handler for the Microservices TestServer.