Grpc.Net.ClientFactory package provides gRPC integration with the IHttpClientFactory and it comes with the convinient extension method used to register gRPC clients: IServiceCollection.AddGrpcClient<TClient>().
The problem is that this method registers TClient with the transient lifetime, which is, in turn, means that it's imposible to inject it into the singleton service. And looks like there is no possibility to configure it somehow (at least the AddGrpcClient source code has nothing on that).
So the question is: how to correclty inject the gRPC client to the singleton service and benefit from the IHttpClientFactory integration at the same time? Should I inject some client factory instead?
How about using named Grpc client to register the client, using like this.
// Register on the Startup.cs
services
.AddGrpcClient<Catalog.CatalogClient>("Catalog", o =>
{
o.Address = new Uri("https://localhost:5001");
});
// Create your service
public class OrderingService : IOrderingService
{
private readonly Catalog.CatalogClient _client;
public OrderingService(GrpcClientFactory grpcClientFactory)
{
_client = grpcClientFactory.CreateClient<Catalog.CatalogClient>("Catalog");
}
}
// Register the service as singleton at Startup.cs
service.AddSingleton<IOrderingService, OrderingService>();
// now _client instance is persist as long as you doesn't do anything weird with it
The grpc client might be a new instance each time (transient), but the underlying HttpClientMessageHandler "is managed".
I see no problems injecting a transient instance into a singleton service.
Related
I'm using Steeltoe to implement Event Messaging, with RabbitMQ, between microservices, but I'm having an issue when I register my Listener service and it not recognizing other DI services.
In my Startup.cs file I have my services being registered as follows:
public void ConfigureServices(IServiceCollection servcies)
{
...
// Add my custom service as a scoped service
services.AddScoped<IMyService>(provider => new MyService());
var rabbitSection = configuration.GetSection(RabbitOptions.PREFIX);
services.Configure<RabbitOptions>(rabbitSection);
services.AddRabbitServices();
services.AddRabbitAdmin();
services.AddRabbitTemplate();
// Add Rabbit Listener Service
services.AddSingleton<MyRabbitListenerService>();
services.AddRabbitListeners<MyRabbitListenerService>();
...
}
... then in my MyRabbitListenerService.cs class:
public class MyRabbitListenerService
{
private readonly IMyService _myService;
public MyRabbitListenerService(IMyService myService)
{
_myService = myService;
}
[RabbitListener("MyQueue")]
public async Task MessageListener(byte[] message)
{
// Do stuff ...
}
}
When I run this, I'm getting an error indicating the IMyService couldn't be injected into the Listener service as it wasn't registered. I can't work out why this isn't working. Is it because I'm trying to inject a scoped service into a singleton service?
UPDATE
I did some testing and changing IMyService from a scoped service to a singleton made it work. Now I need to figure out how to get around this because in my situation it doesn't make sense to register IMyService as a singleton.
The reason for this error is that you cannot consume a scoped service from a singleton. Scoped has per request semantics, which for messaging doesn't make sense. Perhaps you mean AddTransient? That works. If this doesn't work for you, Can you give more detail on why MyService cannot be just transient?
I've read that HttpMessageHandlers are recycled every 2 minutes, but I'm not sure if a new one is assigned to an existing HttpClient?
I've tested it out by using SetHandlerLifetime(TimeSpan.FromSeconds(5)); and even after 2 minutes with countless requests, the httpClient is continuing to work, which is a good sign?
Does that mean that I have no cause for concern with DNS changes/socket exhaustion?
Inside ConfigureServices method:
var myOptions = app.ApplicationServices.GetService<IOptionsMonitor<MyOptions>>();
HttpClient httpClient = app.ApplicationServices.GetService<IHttpClientFactory>().CreateClient();
MyStaticObject.Configure(myOptions, httpClient);
EDIT: Added some sample code.
There are a few things to look at here, with the first being does MyStaticObject actually need to be static? If it does, I would recommend instead registering it as a Singleton so that you can still leverage dependency injection. Once you have done that, you can register IHttpClientFactory and use it from your code. Your ConfigureServices method may end up looking something like this
public void ConfigureServices(IServiceCollection services)
{
//The base extension method registers IHttpClientFactory
services.AddHttpClient();
services.AddSingleton<IMySingletonObject, MySingletonObject>();
}
Then in your consuming class, MySingletonObject in this case, you would configure it as such
public class MySingletonObject
{
private readonly IHttpClientFactory _clientFactory;
public MySingletonObject(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task SomeMethodThatUsesTheClient()
{
var client = _clientFactory.CreateClient();
//use the client
}
}
The reason for this is that IHttpClientFactory handles the lifetime and pool concerns for us. Per the docs:
Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
This happens when you make the CreateClient call, so you want to do this inside the code using the client, as opposed to on startup of your application.
As a side note, if you do not need this class to be a singleton at all, you can use the extenion services.AddHttpClient<IMyClass, MyClass>() and inject an HttpClient directly into the class. The DI container will handle getting a client from the factory behind the scenes for you.
public static IServiceCollection AddApiClient(this IServiceCollection services,
Action<RgCommunicationClientOptions> action)
{
services.AddHttpClient<ISomeApiClient, SomeApiClient>()
.AddPolicyHandler(GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromMinutes(4));
services.AddOptions();
services.Configure(action);
return services;
}
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
//.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
services.AddApiClient(options => options.BaseAddress = configuration.GetSection("ExternalServices:SomeApi")["Url"]);
You can inject HttpClient like above in the class via dependency injection. I have used Poly extension to add RetryPolicy. Also you can set the LifetTime of the handler by using SetHaandlerLifeTime. This way you can individually configure client for each client. In the logs you can see that httpHandler expires after 4 mins for the respective call. I have used the extension method to pass the options in the method, getting values via app settings.
Hope this helps.
Blockquote
I have a scoped service:
public class GetLatestStatus:IGetLatestStatus{
private HttpClient _httpClient;
private readonly int _status;
public GetLatestStatus(HttpClient httpClient){
_httpClient = httpClient;
_date= GetStatusFromService();
}
public string GetStatus(){
return _status;
}
private string GetStatusFromService(){
Logger.Info($"Calling service...");
var request = new HttpGetRequest{Url = "http://some.service/get/status"};
var result = _httpClient.Get(request).Result;
return result.Status;
}
}
Here is how it is defined in the startup:
public virtual void ConfigureServices(IServiceCollection services){
services.AddScoped<IGetLatestStatus, GetLatestStatus>()
.AddHttpClient<IGetLatestStatus, GetLatestStatus>();
services.AddTransient<ISomeClass1, SomeClass1>();
services.AddTransient<ISomeClass2, SomeClass2>();
services.AddTransient<ISomeClass3, SomeClass3>();
}
It is being used by three transient classes.
The intent of this class is that _status is defined only once, when the request comes in. Then it is stored throughout the lifecycle of the request.
Instead, it seems that GetStatusFromService() is being called three times, one per transient class, when the request first comes in.
How do I make this class work the way I intended? I thought that defining something as a Scoped Service means that there's only one copy for the lifecycle of the request. Thank you all for the help!
TL:DR
It happens because you register GetLatestStatus like this after scoped registration .AddHttpClient<IGetLatestStatus, GetLatestStatus>();
So may create another class to store the status and register it as scoped. Then use the Http Configured service to reach the service from it
According to MSDN;
To configure the above structure, add HttpClientFactory in your application by installing the Microsoft.Extensions.Http NuGet package that includes the AddHttpClient() extension method for IServiceCollection. This extension method registers the DefaultHttpClientFactory to be used as a singleton for the interface IHttpClientFactory. It defines a transient configuration for the HttpMessageHandlerBuilder. This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.
Please check the link for more information https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
Everywhere I can see three main approaches to create clients (basic, named, typed) in DI, but I have found nowhere if to inject IHttpClientFactory or HttpClient (both possible).
Q1: What is the difference between injecting IHttpClientFactory or HttpClient please?
Q2: And if IHttpClientFactory is injected, should I use factory.CreateClient() for each call?
Summary
HttpClient can only be injected inside Typed clients
for other usages, you need IHttpClientFactory
In both scenarios, the lifetime of HttpClientMessageHandler is managed by the framework, so you are not worried about (incorrectly) disposing the HttpClients.
Examples
In order to directly inject HttpClient, you need to register a specific Typed service that will receive the client:
services.AddHttpClient<GithubClient>(c => c.BaseAddress = new System.Uri("https://api.github.com"));
Now we can inject that inside the typed GithubClient
public class GithubClient
{
public GithubClient(HttpClient client)
{
// client.BaseAddress is "https://api.github.com"
}
}
You can't inject the HttpClient inside AnotherClient, because it is not typed to AnotherClient
public class AnotherClient
{
public AnotherClient(HttpClient client)
{
// InvalidOperationException, can't resolve HttpClient
}
}
You can, however:
1. Inject the IHttpClientFactory and call CreateClient(). This client will have BaseAddress set to null.
2. Or configure AnotherClient as a different typed client with, for example, a different BaseAdress.
Update
Based on your comment, you are registering a Named client. It is still resolved from the IHttpClientFactory.CreateClient() method, but you need to pass the 'name' of the client
Registration
services.AddHttpClient("githubClient", c => c.BaseAddress = new System.Uri("https://api.github.com"));
Usage
// note that we inject IHttpClientFactory
public HomeController(IHttpClientFactory factory)
{
this.defaultClient = factory.CreateClient(); // BaseAddress: null
this.namedClient = factory.CreateClient("githubClient"); // BaseAddress: "https://api.github.com"
}
Sadly I cannot comment, but only Post an answer. Therefore I suggest you should check out the following Links:
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Regarding your Questions it more or Less boils down to this:
Q1 -> IHttpClientFactory handles the connection pools of HttpClient instances and this will help you regarding load and dispose problems as discribed in the links, if the HttpClient is used wrong.
Q2 -> yes you should use factory.create client according to microsoft docs
I'm using Microsoft.AspNetCore.SignalR (1.1.0)
I have some services in startup.cs:
services.AddSingleton<IHostedService, MySingletonService>();
services.AddScoped<MyScopedService >();
Now I want to use this service in my hub, so I inject it:
private readonly MyScopedService _cs;
private readonly MySingletonService _ss;
public MyHub(MyScopedService cs, MySingletonService ss)
{
_cs = cs;
_ss= ss;
}
This works only for the scoped service, as soon as the service is singleton the hub never gets called and cannot connect in the browser. Why is this not possible? I just want the existing instance of the singleton service, call a method and then let it go again.
This has nothing to do with SignalR. Since I created my service as an IHostedService I would also need to inject it as an IHostedService which is impossible because I have multiple IHostedServices and only want this specific one. The solution is to inject as a normal singleton and then start it.
services.AddSingleton<MySingletonService>();
//Start the background services
services.AddHostedService<BackgroundServiceStarter<MySingletonService>>();
I found the solution here:
https://stackoverflow.com/a/51314147/1560347