how to inject a httprequestmessage / endpoint - c#

I have a controller in my REST API, where I do a HttpRequestMessage. The way I am doing it right now is by using the IConfiguration interface to fetch the endpoint as a variable:
public class MyController : Controller
{
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
public MyController(IConfiguration configuration, HttpClient httpClient){
_configuration = configuration;
_httpClient = httpClient;
}
...
...
[HttpGet]
public async Task<IActionResult> Get(){
...
...
var httpRequest = new HttpRequestMessage(HttpMethod.Get, _configuration["MY_ENDPOINT"]);
await _httpClient.SendAsync(httpRequest);
...
...
return Ok();
}
The thing is, that it is apparently better to have the api endpoint injected via an interface and I honestly don't know what or how that is done.
I do inject the HttpClient and the IConfiguration, but that's something I've done several times and seen others do. But just injecting an endpoint (without IConfiguration), seems unfamiliar for me. The ... is just because I've taken out code that doesn't have influence on the question.
Is there any simple way to just inject the endpoint - and is it just me that don't understand the reason for it?
I guess I have to create an interface and in that some logic that just returns the endpoint? But isn't that just double work?
MY SOLUTION:
the only workaround I can think of at the moment is just by injecting a string:
private readonly string _myEndpoint;
and then inject it:
_myEndpoint = Environment.GetEnvironmentVariable("MY_ENDPOINT");
and finally use that in my httpRequestMessage:
var httpRequest = new HttpRequestMessage(HttpMethod.Get, _myEndpoint);
That's not an interface, but again I don't use the IConfiguration-interface and don't write a lot of unneeded code.
If any better / smarter suggestion, then please shout out.

There is a way that you can load "options" into the service collection via:
services.Configure<EndpointConfig>(Configuration.GetSection("EndPointConfig"));
The EndpointConfig here is a class that you would have to define:
public class EndpointConfig
{
public string EndpointUrl {get;set;}
}
In this particular example the appsettings.json "EndPointConfig" would need a EndpointUrl, here's a rough example:
{
"EndPointConfig" : {
"EndpointUrl" : "https://localhost"
}
}
then when you get to your controller you pass in the config like so:
private readonly EndpointConfig _configuration;
private readonly HttpClient _httpClient;
public MyController(IOptions<EndpointConfig> configuration, HttpClient httpClient){
_configuration = configuration.Value;
_httpClient = httpClient;
}
There is some good documentation around this if you would like to try it: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1
If you don't want to define a EndpointConfig section in your appsettings.json as your describing in the comments then you would simply configue i with the Configuration object:
services.Configure<EndpointConfig>(Configuration);
Now it will search for the property name (in this case EndpointUrl) in the base object of your appsettings json:
{
"EndpointUrl" : "https://localhost"
}
If you want to look for a different name i.e. My_Endpoint you would have to simply rename your property:
public class EndpointConfig
{
public string My_Endpoint {get; set;}
}

Related

Ninject Dependency Resolver

The client which I am calling looks like this
public class CmsClient : ICmsClient
{
private readonly HttpClient _client;
private readonly ICmsSettings _cmsSettings;
public CmsClient(HttpClient client, ICmsSettings cmsSettings)
{
_client = client;
_cmsSettings = cmsSettings;
}
}
In NInjectWebCommon.cs file I am resolving like this.
kernel.Bind<ICmsClient>().To<CmsClient>()
This is not working as the constructor is expecting httpClient and cmsSetting class.
How can I resolve this?
You need to tell your kernel how to resolve those types -
kernel.Bind<ICmsClient>().To<CmsClient>();
kernel.Bind<ICmsSettings>().To<CmsSettings>();
kernel.Bind<HttpClient>().ToSelf();
var client = kernel.Get<ICmsClient>();

How to pass/inject more than one HttpClient parameter to a typed HttpClientClass?

I want to register a typed HttpClient as here Microsoft docs.
Basically, the approach should be
services.AddHttpClient();
normally the pattern of these classes receive only the HttpClient class as a parameter and you implement the logic to call the endpoint. In my case, I need to use 2 HttpClient inside my MyHttpClient, one that pings the endpoint and the other one that talks with an IdentityProvider to discover the refreshEndpoints to refresh my cookies.
public class MyHttpClient : IMyHttpClient
{
public MyHttpClient (HttpClient httpClient,
HttpClient refreshHttpClient)
{
}
}
If I am trying to resolve from a controller an IMyHttpClient, I get an error saying it can't resolve an HttpClient.
In the GitHub code on line 43 AddHttpClient you can see that is calling
DefaultTypedHttpClientFactory.
If you go to the implementation of the DefaultTypedHttpClientFactory implementation you will notice that is a generic type. And when it calls CreateClient it only passes one parameter to the constructor on line 39.
The only workaround I am seeing here is to not create a typed client and register a normal class that receives an IHttpClientFactory and create and configure my clients on the fly, not as typed.
Any other idea?
You can't. You'll either need to inject another service layer or IHttpClientFactory directly
Another service
public class MyRefreshClient
{
private readonly HttpClient _httpClient;
public MyRefreshClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
...
}
public class MyHttpClient : IMyHttpClient
{
private readonly HttpClient _httpClient;
private readonly MyRefreshClient _refreshClient;
public MyHttpClient(HttpClient httpClient, MyRefreshClient refreshClient)
{
_httpClient = httpClient;
_refreshClient = refreshClient;
}
}
Then:
services.AddHttpClient<MyRefreshClient>(c => { ... });
services.AddHttpClient<MyHttpClient>(c => { ... });
Inject IHttpClientFactory (and use named clients):
public class MyHttpClient : IMyHttpClient
{
private readonly HttpClient _httpClient;
private readonly HttpClient _refreshClient;
public MyHttpClient(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("MyHttpClient");
_refreshClient = httpClientFactory.CreateClient("MyRefreshClient");
}
}
Then:
services.AddHttpClient("MyHttpClient", c => { ... });
services.AddHttpClient("MyRefreshClient", c=> { ... });

Cannot inject HttpClient in typed client while using with IMediatR library

According to examples provided by ASP.NET Core 2.2 documentation in MSDN, it is possible to inject HttpClient to a typed clients (service-classes) by adding the following line to Startup.cs:
// Startup.cs
services.AddHttpClient<GitHubService>();
From controller class it will look like (from now I will use GitHub as a simplification for a domain model):
// GitHubController.cs
public class GitHubController : Controller
{
private readonly GitHubService _service;
public GitHubController(GitHubService service)
{
_service = service;
}
}
However, I use MediatR library in my project, so my project structure looks a bit different. I have 2 projects - GitHubFun.Api, GitHubFun.Core - ASP.NET Core 2.2 API project and .NET Core 2.2 class library respectively.
My controller:
// GitHubController.cs
public class GitHubController : Controller
{
private readonly IMediator _mediator;
public GitHubController(IMediator mediator)
{
_mediator= mediator;
}
public async Task<IActionResult> GetGitHubRepositoryInfo(
GetGitHubRepositoryCommand command)
{
_mediator.Send(command);
}
}
And my handler class:
// GetGitHubRepositoryHandler.cs
public class GetGitHubRepositoryHandler :
IRequestHandler<GetGitHubRepositoryCommand , GetGitHubRepositoryCommandResult>
{
private HttpClient _httpClient;
public GetGitHubRepositoryHandler(HttpClient httpClient)
{
_httpClient = httpClient;
}
}
When I make HTTP request and call an API method, it successfully injects IMediator, but throws an exception on _mediator.Send(command) line.
Exception body:
System.InvalidOperationException: Error constructing handler for request of type MediatR.IRequestHandler`2[IDocs.CryptoServer.Core.Commands.ExtractX509Command,IDocs.CryptoServer.Core.Commands.ExtractX509CommandResult]. Register your handlers with the container. See the samples in GitHub for examples. ---> System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'IDocs.CryptoServer.Core.Handlers.ExtractX509CommandHandler'
(ExtractX509CommandHandler - is just a real domain model, instead of GetGitHubRepositoryHandler).
It seems that ASP.NET Core DI cannot resolve DI and inject HttpClient to handler.
My Startup.cs has the following lines:
services.AddHttpClient<ExtractX509CommandHandler>();
services.AddMediatR(
typeof(Startup).Assembly,
typeof(ExtractX509CommandHandler).Assembly);
I found a solution. For some reasons, in this case we need to pass IHttpClientFactory from Microsoft.Extensions.Http.dll instead of HttpClient to handler class. I just changed one line, it was:
public GetGitHubRepositoryHandler(HttpClient httpClient)
and now:
public GetGitHubRepositoryHandler(IHttpClientFactory httpClientFactory)
and now it works as it should. I don't know why it works, so it will be perfect, if someone could explain what is the difference between injecting IHttpClientFactory and HttpClient to the class.
I found a solution.
I'm in ASP.NET Core 3.1.
My handler class :
public class GetProductsListQueryHandler : IRequestHandler<GetProductsListQueryModel, IEnumerable<Product>>
{
private readonly HttpClient _httpClient;
public GetProductsListQueryHandler(HttpClient httpClient)
{
_httpClient = httpClient;
}
You need to implement your HttpClient in your Startup like this :
services.AddHttpClient<IRequestHandler<GetProductsListQueryModel,IEnumerable<Product>>, GetProductsListQueryHandler>();
And it works! ;)

Unit Tests and incapsulation

For example, I have a class, working with HttpClient
public class DomainActions : IDomainActions
{
private readonly HttpClient _client;
private readonly IConfiguration _configuration;
public DomainActions(IConfiguration configuration)
{
_configuration = configuration;
_client = new HttpClient()
{
BaseAddress = new Uri(_configuration.GetSection("DomainRegistration:BaseAddress").Value)
};
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _configuration.GetSection("DomainRegistration:Token").Value);
}
public async Task<List<DomainDto>> GetDomainListAsync()
{
var responseMessage = await _client.GetAsync("domains");
return await ProcessingDomainListResponseAsync(responseMessage);
}
then we resolve it by the following way:
services.AddTransient<IConfiguration>(....);
services.AddTransient<IDomainActions, DomainActions>();
and client class:
public class AddMxRecordToRegistrator
{
protected readonly IDomainActions domainActions;
public AddMxRecordToRegistrator(IDomainActions domainActions )
{
this.domainActions = domainActions ;
}
public async Task CreateDomainRecordAsync()
{
await domainActions.CreateDomainRecordAsync(queueItem.DomainForRegistration.DomainName, new DomainRegistrationCore.Models.DomainRecordDto
{
Content = queueItem.MxRecord,
Name = String.Empty,
Priority = 0,
Ttl = 3600,
Type = DomainRecordType.MX.ToString(),
Regions = null
});
ok, it works fine.
Right now, I want to create unit test for AddMxRecordToRegistrator class , but I don't want to use real httpClient. How to do it? Of course, I can add one more dependency:
public class DomainActions : IDomainActions
{
private readonly HttpClient _client;
private readonly IConfiguration _configuration;
public DomainActions(IConfiguration configuration, HttpMessageHandler httpMessageHandler)
{
_configuration = configuration;
_client = new HttpClient(httpMessageHandler)
{
BaseAddress = new Uri(_configuration.GetSection("DomainRegistration:BaseAddress").Value)
};
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _configuration.GetSection("DomainRegistration:Token").Value);
}
public DomainActions(IConfiguration configuration) : this(configuration, new HttpClientHandler())
{
}
public async Task<List<DomainDto>> GetDomainListAsync()
{
var responseMessage = await _client.GetAsync("domains");
return await ProcessingDomainListResponseAsync(responseMessage);
}
then modify DI composition root:
services.AddTransient<IConfiguration>(....);
services.AddTransient<HttpMessageHandler>(....);
services.AddTransient<IDomainActions, DomainActions>();
but then why client part (in our case composition root) should know anything about internal detail of DomainActions only because we need to create unit test? It like we violate incapsulation for unit tests. How to implement it correctly?
To expand on the comment from #CamiloTerevinto, AddMxRecordToRegistrator should depend on IDomainActions via dependency injection, i.e. that interface should be the argument passed to its constructor.
From an encapsulation perspective, AddMxRecordToRegistrator shouldn't know that DomainActions depends on IConfiguration or HttpMessageHandler. It shouldn't even know that DomainActions exists, because that's a concrete class, and AddMxRecordToRegistrator should depend on interfaces, not concrete classes.
but then why client part (in our case composition root) should know
anything about internal detail of DomainActions only because we need
to create unit test?
Composition root is only place in application which will "know" about all lower level dependencies.
"Composition" root's role is to compose required classes with runtime implementations.
Class AddMxRecordToRegistrator clearly depends on abstraction IDomainActions, so for unit testing AddMxRecordToRegistrator you just pass fake implementation of IDomainActions.

Creating stub for `private static readonly` field

Due on Improper Instantiation problem it is recommended to create private static readonly instance of HttpClient.
Due on lack of time I have injected mocked client into test method with client as their parameter.
The problem is how can I in simple way inject mock into private static readonly HttpClient field of SingleHttpClientInstanceController?
how can I in simple way inject mock into private static readonly
HttpClient field of SingleHttpClientInstanceController?
Answer: There is no simple way.
Suggestion:
Abstract the resource behind an accessor
public interface IHttpClientAccessor {
HttpClient HttpClient { get; }
}
and inject that into the dependent controller.
public class SingleHttpClientInstanceController : ApiController {
private readonly HttpClient HttpClient;
public SingleHttpClientInstanceController(IHttpClientAccessor httpClientAccessor) {
HttpClient = httpClientAccessor.HttpClient;
}
// This method uses the shared instance of HttpClient for every call to GetProductAsync.
public async Task<Product> GetProductAsync(string id) {
var hostName = HttpContext.Current.Request.Url.Host;
var result = await HttpClient.GetStringAsync(string.Format("http://{0}:8080/api/...", hostName));
return new Product { Name = result };
}
}
The same should also be done for accessing HttpContext which is what was recently introduced in Asp.Net-Core's IHttpContextAccessor
An implementation of the IHttpClientAcessor can look something like this
public class HttpClientAccessor : IHttpClientAccessor {
static readonly Lazy<HttpClient> client = new Lazy<HttpClient>(() => new HttpClient());
public HttpClient HttpClient { get { return client.Value; } }
}
So now for tests you can inject mock of the dependency.
If using a DI container remember to register the accessor as a singleton as well.

Categories

Resources