Injecting IHttpClientFactory in structuremap - c#

We are using an older version of Structuremap (3.1.9.463). It's been a while since I used structuremap and HttpClients alongside and I wonter how to properly inject the IHttpClientFactory in structuremap.
Simply using bootStrapper.For<IHttpClientFactory>().Use<HttpClient>(); won't work
A usage example is
public class DialogClient : IDialogClient
{
private readonly HttpClient _client;
public DialogClient(IHttpClientFactory httpClientFactory)
{
_client = httpClientFactory.CreateClient();
_client.BaseAddress = new Uri(ConfigurationManager.AppSettings["Dialog:url"]);
}
}
The project also use .NET Framework, not Core.

implement the interface
class MyHttpClientFactory: IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
// logic for creating client here
}
}
and then register it
For<IHttpClientFactory>().Singleton().Use<MyHttpClientFactory>();

Related

How to test interface which has dependency injection with option pattern?

Let's say I have a web api application which has this service interface.
public interface IMyService
{
Task<int> Process(string processType);
}
and this is my service class. I am using option pattern.
public class MyService : IMyService
{
private readonly MyOptions _myOptions;
private readonly MyContext _myContext;
private readonly IHttpClientFactory _httpClientFactory;
public MyService(IOptions<myOptions> myOptions,
MyContext myContext,
IHttpClientFactory httpClientFactory)
{
_myOptions = myOptions.Value;
_myContext = myContext;
_httpClientFactory = httpClientFactory;
}
}
and this is how I register it in Program.cs. I am using .NET 6 framework.
var builder = WebApplication.CreateBuilder(args);
var myConnStr =
builder.Configuration.GetConnectionString("MyConnection");
// EF Core
builder.Services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(myConnStr);
});
builder.Services.AddOptions().Configure<MyOptions>(builder.Configuration.GetSection("ABC"));
builder.Services.AddHttpClient();
builder.Services.AddScoped<IMyService, MyService>();
// some code are removed for brevity
How do I test Process method in my service using Xunit? Note: Although the web api is calling this method, I do not want to test the web api. The reason is because this is actually a background process. Thus there won't be much thing return in the controller (web api). I also don't feel the need to mock it if possible to simplify the integration test.
I managed to do something like this.
public class MyServiceTest
{
private const string sqlServerConnection = "xxx";
private IMyService _myService;
private MyContext _myContext;
public MyServiceTest()
{
_configuration = new ConfigurationBuilder().AddJsonFile("appsettings.test.json").Build();
}
[Fact]
public async Task WhenProcessIsInValid_ThenReturn0()
{
// Arrange
SetupSqlServerContext();
await SetupMockData();
// _myService = new MyService(_myContext);
}
}
private void SetupSqlServerContext()
{
var options = new DbContextOptionsBuilder<MyContext>();
options.UseSqlServer(sqlServerConnection);
_myContext = new SqlServer.MyContext(options.Options);
}
I am stuck on creating the instance for MyService. How do I pass it IHttpClientFactory and IOptions<yOptions> to its constructor?
Use one of the mocking frameworks like Moq, for example, to mock the IHttpClientFactory.
It will look similar to this
var factoryMock = new Mock<IHttpClientFactory>();
factoryMock
.Setup(factory => factory.CreateClient(It.IsAny<string>()))
.Returns(httpClientMock.Object);
Check this answer to learn how to mock HttpClient.
For IOptions<MyOptions> it's even simpler, just create an instance of it using this code
var options = Options.Create(new MyOptions{ });
And finally, instantiate the MyService
_myService = new MyService(options, _myContext, factoryMock.Object);

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! ;)

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