Pass ILogger<T> to Polly retry policy without HttpRequestMessage - c#

When making HTTP calls using an instance of HttpClient wrapped in Polly's retry policy and injected into a controller using dependency injection, I want to send ILogger<T> from the call site to a delegate of the retry policy (e.g., onRetry), so logs are registered more appropriately.
Polly docs explain how to achieve this by sending ILogger<T> from the calls site to the retry delegates leveraging Context encapsulated in an HttpRequestMessage request.
However, this solution works when you leverage a method of the HttpClient that takes HttpRequestMessage in one of its overloads. For instance, client.SendAsync.
However, not every method of HttpClient take HttpRequestMessage. For instance, I'm using client.GetStreamAsync, which none of its overloads take HttpRequestMessage.
In this case, I wonder how you would pass the Ilogger<T> to Polly's retry delegates.

Options that does not work for your use case
Using the Context object with HttpRequestMessage
As you have stated in your question this is not applicable, since you don't have a HttpRequestMessage instance on which you could access the Context via the request.GetPolicyExecutionContext call.
Using AddPolicyHandler + IServiceProvider
The AddPolicyHandler has an overload which provides access to the IServiceProvider and to the HttpRequestMessage. You could obtain an ILoggerFactory via provider.GetRequiredService<ILoggerFactory>() and then you could call factory.CreateLogger<T>.
The problem with this approach is that you don't know T at policy registration time, since you want to use the Controller as T.
Options that could work for your use case
Defining the policy inside your Controller
If you would define the policy inside the same class where you have the intention to use it then you could access the ILogger<YourController>.
There are two drawbacks of this approach:
You have to define (more or less) the same policy in every place where you want to use it
You have to explicitly call the ExecuteAsync
The first issue can be addressed via the PolicyRegistry
Registering the policy into PolicyRegistry and using Context
You can register your policy/ies into a PolicyRegistry and then you can obtain them (via IReadOnlyPolicyRegistry) inside your controller. This approach lets you define your policy in the way that you can retrieve an ILogger from the Context inside the onRetry. And you can specify the Context when you call the ExecuteAsync
var context = new Polly.Context().WithLogger(yourControllerLogger);
await policy.ExecuteAsync(async (ct) => ..., context);
Registering the policy into PolicyRegistry and using try-catch
The previous approach used the Context to transfer an object between the policy definition and its usage. One can say that this separation is a bit fragile since the coupling between these two is not explicit rather via a magic Context object.
An alternative solution could be to perform logging only inside your the ExecuteAsync to avoid the usage of the Context
await policy.ExecuteAsync(async () =>
try
{
...
}
catch(Exception ex) //filter for the retry's trigger exception(s)
{
yourControllerLogger.LogError(...);
});
As you can see none of the above solutions is perfect since you want to couple the policy and its usage via logging.
UPDATE #1
I'm not a big fan of defining policy inside a controller, because I generally reuse a policy (and accordingly the HttpClientFactory) in different controllers.
As I said above, this is one option out of three. The other two options do not require you to define your policy inside the controller class. You can define them inside the startup
var registry = new PolicyRegistry()
{
{ "YourPolicyName", resilientStrategy }
};
services.AddPolicyRegistry(registry);
and then retrieve the given policy inside the controller
private readonly IAsyncPolicy policy;
public YourController(IReadOnlyPolicyRegistry<string> registry)
{
policy = registry.Get<IAsyncPolicy>("YourPolicyName"):
}
I suppose there is no other cleaner solution
If you want to / need to use the controller's logger inside the onRetry delegate then I'm unaware of any cleaner solution.
If you want to use that logger to be able to correlate the controller's log with the policy's log then I would rather suggest to use a correlation id per request and include that into your logs. Steve Gordon has a nuget package called correlationId which can help you to achieve that.

Related

Is it ok to call `Task.Result` in ASP.NET configuration methods?

I want to configure a HttpClient to have a default authorization header that results from an async call like this:
builder.Services.AddHttpClient("client", (serviceProvider, client) =>
{
client.DefaultRequestHeaders.Authorization =
serviceProvider.GetService<IAuthProvider>()?.GetAuthHeaderAsync().Result;
});
Is it ok to call Task.Result in configuration methods that do not support async delegates? or should I do this:
builder.Services.AddHttpClient("client", async (serviceProvider, client) =>
{
client.DefaultRequestHeaders.Authorization =
await serviceProvider.GetService<IAuthProvider>()?.GetAuthHeaderAsync();
});
When I do this second option I'm getting a warning: Avoid using
'async' lambda when delegate type returns 'void'
Dependency injection is one place where the "use async all the way" general advice isn't applicable, since no DI/IoC container supports asynchronous resolution.
One way to work around this is to use "asynchronous factory" types; i.e., you define an HttpClient factory that can asynchronously produce a client on demand.
Another approach is to just do DI/IoC injection synchronously, usually blocking on all asynchronous construction at startup time, before the application has actually started.
A final approach is more advanced: you define a type that wraps all the APIs you need (with asynchronous signatures), and you hide the asynchronous initialization behind an async lazy (or a single-item async cache) within that type.
Regarding your specific use case, is the authentication header something that is only requested once, and remains constant for the rest of the app? If so, I would say to just synchronously block on it at startup. If it's something that can change, then you're probably better off handling authentication as a message handler, not at the point of constructing a client.

Resolving dependencies in a Func<T> that is moved to a separate class file

I'm working with a codebase (Minimal APIs : .NET 6) which exposes a custom middleware (UseCustomMiddleware) that is added to IApplicationBuilder via extension methods.
The second parameter of UseCustomMiddleware is a Func<HttpRequest, Identity, Message, ... Task<(bool Pass, Error Error)> that act as a predicate for providing authentication mechanism.
Here's the layout in Program.cs:
builder.Services.AddScoped<AuthenticationService>();
var app = builder.Build();
app.UseCustomMiddleware<IContract,Methods>("/", async (httpRequest, accessibility, message, ...) =>
{
//resolving dependencies here is not a problem.
var authenticationService = app.Services.CreateScope().ServiceProvider.GetRequiredService<AuthenticationService>();
//the rest of logic continues...
});
Everything works fine but the logic inside lambda is getting lengthier and lengthier and I need to move that to a separate class file.
I could create a static class and define the same static method with the signature of Func<...> and reference it in place of lambda but then I don't know how to resolve dependencies in there.
What is the proper way to achieve this?
Not sure what UseCustomMiddleware is but you don't need app.Services.CreateScope().ServiceProvider... (also you don't dispose the scope which is bad). Middleware should have access to HttpContext, which has RequestServices property which you should use to resolve services. In theory you can try to get it from HttpRequest:
app.UseCustomMiddleware<IContract,Methods>("/", async (httpRequest, accessibility, message, ...) =>
{
var authenticationService = httpRequest.HttpContext.RequestServices.GetRequiredService<AuthenticationService>();
});
Also see samples in the docs, especially for middlewares extracted into classes, I would argue they are more suitable for complex logic then ones with Func handlers.

Accessing IOptions and DbContext within ConfigureServices

I've started to implement health checks in my .NET Core Web API. There are two health checks, one for checking if the SQL Server has any pending migrations and the other is checking if another API is live. Both added within ConfigureServices in Startup class.
In order to do the migration check, I need to access the DbContext which has already been added to DI using AddDbContext and to check the API, I need to get the API base url from configuration which is already in DI using services.Configure<>. I use the following code to get access to the DbContext.
I'm using AspNetCore.HealthChecks.Uris package to use AddUrlGroup health check.
var sp = services.BuildServiceProvider();
var dbContext = sp.GetService<AppDbContext>();
var apis = sp.GetService<IOptions<InternalServicesConfiguration>>().Value;
services.AddHealthChecks().AddCheck("Database", new SqlDatabaseHealthCheck(dbContext), tags: new[] { "ready" })
.AddUrlGroup(new Uri(new Uri(apis.Api1BaseUri), "/health/live"), HttpMethod.Get, "API 1", HealthStatus.UnHealthy, new []{"ready"});
But services.BuildServiceProvider() shows the following warning:
Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'
I can get the api base urls using
_configuration.GetSection("InternalServicesConfiguration").Get(typeof(InternalServicesConfiguration));
But I can't think of an alternative way to access the DbContext.
Any help much appreciated.
You can register your healthcheck like this:
services.AddHealthChecks()
.AddCheck<ExampleHealthCheck>("Database");
And then just inject your DbContext into ExampleHealthCheck class, which has to implement IHealthCheck interface
There are some healthchecks you can use directly for EF in the official docs
But if you want to write any custom or more complex checks, your best bet might be to create a class that implements the IHealthCheck interface, where you can inject anything you want.
Also from the docs about Custom health checks, an example:
public class ExampleHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default(CancellationToken))
{
var healthCheckResultHealthy = true;
if (healthCheckResultHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("A healthy result."));
}
return Task.FromResult(
HealthCheckResult.Unhealthy("An unhealthy result."));
}
}
which, as kebek alerady answered, you will register like
services.AddHealthChecks()
.AddCheck<ExampleHealthCheck>("example_health_check");
With regard to accessing IOptions (please see updated health check). Is there another way other than getting it using _configuration.GetSection("").Get<>()
You could register those options in a following way (in ConfigureServices):
services.Configure<InternalServicesConfiguration>(Configuration.GetSection("InternalServicesConfiguration"));
And to get those options, in your class just inject IOptions<InternalServicesConfiguration> options, where the options.Value prop is the configuration value

Prevent IHttpClientFactory to create DI scope for handlers

I am using scoped service called IOperationContextProvider to hold some information about my current execution context (called OperationContext).
Whenever I start a new execution path (not only HTTP request, but some async impulses such as queue message, change feed change..), I create a dedicated DI service scope.
Any class can inject the provider and has access to this context (such as correlation ID).
For outgoing requests, I would like to configure to add the correlation ID to outgoing HTTP header, like this:
services.AddHttpClient<IMyClass, MyClass>((serviceProvider, httpClient) =>
{
var contextProvider = serviceProvider.GetRequiredService<IOperationContextProvider>();
var corrId = contextProvider.Context.CorrelationId;
httpClient.DefaultRequestHeaders.Add("x-corr-id", corrId);
});
However, I am unable to do this, because IHttpClientFactory creates scope for each handler it is creating and my context is not reachable from inside the HTTP client configuration. Same goes for adding HTTP message handlers, they are created in the same scope as the handler too.
Official documentation:
The IHttpClientFactory creates a separate DI scope for each handler. Handlers are free to depend upon services of any scope.
Is there any way to reach the same scope as in which the HttpClient itself is being built?
I only have found a way to where for the MyClass, where I also inject HttpClient, I inject the IOperationContextProvider too and configure manually the HttpClient but that is a bit cumbersome because it needs to be done everywhere:
public MyClass(HttpClient httpClient, IOperationContextProvider contextProvider)
{
var corrId = contextProvider.Context.CorrelationId;
httpClient.DefaultRequestHeaders.Add("x-corr-id", corrId);
this._httpClient = httpClient;
}
If you absolutely don’t want the HttpClientFactory to create a service scope, then you can disable this behavior through the HttpClientFactoryOptions.SuppressHandlerScope property. There isn’t a nice API to configure this though, so you will have to do something like this:
var httpClientBuilder = services.AddHttpClient<IMyClass, MyClass>(…);
services.Configure<HttpClientFactoryOptions>(httpClientBuilder.Name, options =>
{
options.SuppressHandlerScope = true;
});
Alternatively, you could also create the delegating handler directly, without going through DI:
services.AddHttpClient<IMyClass, MyClass>(…)
.AddHttpMessageHandler(sp =>
{
var contextProvider = sp.GetService<IOperationContextProvider>()
return new MyHandlerWithoutDI(contextProvider);
});
One of the things thats also suggested is that the shared settings like the defaultrequestheaders be properly setup to avoid race conditions, if you are planning to use this client as a shared resource. This is in reference to your initial proposed workaround.
public MyClass(HttpClient httpClient, IOperationContextProvider
contextProvider)
{
var corrId = contextProvider.Context.CorrelationId;
httpClient.DefaultRequestHeaders.Add("x-corr-id", corrId);
this._httpClient = httpClient;
}
https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/

Should Polly Policies be singletons?

I have a query, IGetHamburgers, that calls an external API.
I've registered the implementation of IGetHamburgers in my DI container as a Singleton. Im using Polly as a Circuitbreaker, if two requests fails the circuit will open.
My goal is that all calls to the Hamburger api should go through the same circuitbreaker, if GetHamburgers fails, then all other calls should fail as well.
How should I use my Policy? Should I register my Policy as a field like this:
private Policy _policy;
private Policy Policy
{
get
{
if(this_policy != null)
{
return this_policy;
}
this._policy = Policy
.Handle<Exception>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
return this._policy;
}
}
public object Execute(.......)
{
return Policy.Execute(() => this.hamburgerQuery.GetHamburgers());
}
OR
public object Execute(.......)
{
var breaker = Policy
.Handle<Exception>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
return breaker.Execute(() => this.hamburgerQuery.GetHamburgers());
}
I guess that the first option is the correct way since then the Policy object will always be the same and can keep track of the exception count and stuff like that.
My question is, will option number two work as well? I've found a lot of samples/examples on Pollys Github but I can't find any "real world" examples where Polly is used together with DI and stuff like that?
I guess that the first option is the correct way since then the Policy object will always be the same and can keep track of the exception count and stuff like that.
Correct. This is described in the Polly wiki here. In brief:
Share the same breaker policy instance across call sites when you want those call sites to break in common - for instance they have a common downstream dependency.
Don't share a breaker instance across call sites when you want those call sites to have independent circuit state and break independently.
See this stackoverflow answer for a more extensive discussion of configuring policies separately from their usage, injecting them to usage sites by DI, and the effects of re-using the same instance (for example a singleton) versus using separate instances, across the full range (at June 2017) of Polly policies.
will option number two work as well?
No (for the converse reason: each call creates a separate instance, so won't share circuit statistics/states with other calls).

Categories