Polly with IDistributedCache and IHttpClientFactory Policy - c#

Using the following code compiles fine, but receives the runtime error below. Seems to be a conflict between the policy only supporting HttpResponseMessage when using IHttpClientFactory?
The end goal is to be able to use several policies like retry, timeout, etc. and if everything is OK cache the result with the cache policy...
Unable to cast object of type
'Polly.Caching.AsyncCachePolicy'1[System.String]' to type
'Polly.IAsyncPolicy'1[System.Net.Http.HttpResponseMessage]'.'
serviceCollection.AddStackExchangeRedisCache(options =>
{
options.Configuration = "...";
});
IPolicyRegistry<string> registry = serviceCollection.AddPolicyRegistry();
var cacheProvider = ServiceProvider.GetRequiredService<IDistributedCache>().AsAsyncCacheProvider<string>();
serviceCollection.AddSingleton(serviceProvider => cacheProvider);
AsyncCachePolicy<string> cachePolicy =
Policy.CacheAsync(
cacheProvider: cacheProvider,
TimeSpan.FromSeconds(30));
registry.Add("CachingPolicy", cachePolicy);
serviceCollection.AddHttpClient<IMyClient, MyClient>()
.AddPolicyHandlerFromRegistry(this.PolicySelector)
private IAsyncPolicy<HttpResponseMessage> PolicySelector(IReadOnlyPolicyRegistry<string> policyRegistry, HttpRequestMessage httpRequestMessage)
{
return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("CachingPolicy");
}

As the error says you can not convert AsyncCachePolicy<string> to IAsyncPolicy<HttpResponseMessage>. Since all AsyncXYZPolicy implements the IAsyncPolicy interface that's why the problem is not coming from here. Rather than from the type parameter.
The AddHttpClient returns an IHttpClientBuilder. There are several extension methods on them like AddPolicyHandlerFromRegistry, AddTransientHttpErrorPolicy or AddPolicyHandler. In all cases you need to register a policy where the return type is HttpResponseMessage.
If you would try to register your cache policy directly via the AddPolicyHandler then it would cause compilation error rather than run-time error. But because you retrieve the policy dynamically from the registry that's why it throws exception at runtime.
How to fix it?
Rather than defining a policy as AsyncCachePolicy<string> you should define it as AsyncCachePolicy<HttpResponseMessage>. To do that you need to change the type parameter of the AsAsyncCacheProvider method.
var cacheProvider = ServiceProvider
.GetRequiredService<IDistributedCache>()
.AsAsyncCacheProvider<HttpResponseMessage>();
You also need to change the cachePolicy's type
AsyncCachePolicy<HttpResponseMessage> cachePolicy =
Policy.CacheAsync(
cacheProvider: cacheProvider,
TimeSpan.FromSeconds(30));
Side note: I would also suggest to store the policy registry key ("CachingPolicy") in a constant and refer to it when you register that and when you retrieve that.
UPDATE #1:
I'm not even sure that you have to call the AsAsyncCacheProvider method at all. Let me double check.
UPDATE #2
After reading the source code of the AsAsyncCacheProvider I've realized that it only supports byte[] or string as a type parameter. This indicates that you can't use the AddPolicyHandler methods here to automatically cache the response.
Rather you have to use the AsyncPolicyCache<string> directly in your MyClient implementation. You need to modify the constructor of the MyClient to recieve an IReadonlyPolicyRegister<string> parameter as well.
private readonly IAsyncPolicy<string> _cachePolicy;
public MyClient(HttpClient client, IReadOnlyPolicyRegistry<string> policyRegistry)
{
_cachePolicy = policyRegistry.Get<IAsyncPolicy<string>>("CachingPolicy");
// ...
}
And inside your exposed method you need to explicitly use ExecuteAsync
await _cachePolicy.ExecuteAsync(context => getXYZ(), new Context("XYZUniqueKey"));
The getXYZ needs to return a string (potentially the response body).

Related

Pass ILogger<T> to Polly retry policy without HttpRequestMessage

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.

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.

Inject AddSqlServer with another service

Is it possible to resolve an instance of ISettingsService from the ConfigureServices method in Startup(.cs) - webapplication?
I've implemented a SettingsService which is able to retrieve the database connectionstring from an external source (secure credentials store). Within the ConfigureServices I need an instance of the ISettingsService in order to get the connectionstring and pass it to the services.AddSqlServer<MyDbContext>(connectionstring) method.
While creating the instance (using var provider = services.BuildServiceProvider(); var settings = provider.GetService<ISettingsProvider>();) Visual Studio displays the next error:
Another developer posted a similar question on StackOverflow and the answer provides a solution in case of AddSingleton/ AddTransient. What is the correct way to apply it on the AddSqlServer call? Or could you provide another solution to avoid the warning/ error message?
The Intellisense comment for .AddSqlServer actually says to use .AddDbContext if you need more control, and that's certainly the correct option.
If you refer to the source code here, you can see that all .AddSqlServer is actually doing is calling .AddDbContext and configuring the options accordingly. We can therefore write our own solution like this:
services.AddDbContext<DbContext>((serviceProvider, options) => {
var settings = serviceProvider.GetService<ISettingsProvider>();
// I don't know what methods your ISettingsProvider makes available
// so adjust accordingly
string connectionString = settings.GetSetting("connectionString");
options.UseSqlServer(connectionString);
});
Of course you can make other changes to the options here, and .UseSqlServer can also take a Action<SqlServerDbContextOptionsBuilder> (options.UseSqlServer(connectionString, opts => opts.EnableRetryOnFailure()), etc.) to further configure it.

C# dotnet core 2 pass data from middleware/filter to controller method

currently we are writing a web application with dotnet core 2.
We actually create some kind of multi-hosting platform where we can register new clients based on the url passed to our application.
However currently we wanted to create a middleware/filter to validate our client's.
Actually what we wanted to do is pull an object from the database and check if it exists, if yes, we want to call the controller method and make the object accessible, if it does not exist, we actually want to abort and show an error page.
What we already have done is created a filter/middleware that does exactly that, however we couldn't figure out a way to access the object that we already pulled in our filter/middleware inside the controller method.
is there actually any documentation for doing that?
I actually tried to figure it out from:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware?tabs=aspnetcore2x
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters
but they don't describe a way to do it, only to actually do something before/after the action.
You could add the object to the context using HttpContext.Items which the docs state:
The Items collection is a good location to store data that is needed only while processing one particular request. The collection's contents are discarded after each request. The Items collection is best used as a way for components or middleware to communicate when they operate at different points in time during a request and have no direct way to pass parameters.
For example, in your middleware:
public class MySuperAmazingMiddleware
{
private readonly RequestDelegate _next;
public MySuperAmazingMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
var mySuperAmazingObject = GetSuperAmazingObject();
context.Items.Add("SuperAmazingObject", mySuperAmazingObject );
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
}
Then later on in your action method, you can read the value:
var mySuperAmazingObject = (SuperAmazingObject)HttpContext.Items["mySuperAmazingObject"];
One way of doing it (not saying it's the only or the best) is to have DI inject a proxy of the object, you set the real value of the object in your middleware, then you can access it from the proxy in the controller.
Note: if you'll pass the proxy object in the method call instead of controller, don't forget to mark it with [FromServices] attribute.
Another way would be adding the object to the request Items property. but when you read it you'll need casting from object to your actual class.

Registering component multiple times

I need to instantiate a concrete class based on data in the HttpRequestMessage. I'm using the following code to configure my Web API service:
var builder = new ContainerBuilder();
builder.RegisterHttpRequestMessage(GlobalConfiguration.Configuration);
builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
builder.RegisterApiControllers(typeof(WebApiApplication).Assembly);
// ... snip ....
container.Register(x =>
{
if (x.IsRegistered<HttpRequestMessage>())
{
var httpRequestMethod = x.Resolve<HttpRequestMessage>();
var tokenHelper = x.Resolve<ITokenHelper>();
var token = tokenHelper.GetToken(httpRequestMethod);
var connectionContext = x.Resolve<ISqlServerConnectionContext>();
connectionContext.Token = token;
return token ?? new NullMinimalSecurityToken();
}
return new NullMinimalSecurityToken();
});
// ... snip ...
var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return container;
Unfortunately, the delegate is called multiple times, with the last one not having a registered HttpRequestMessage, meaning I get back a NullMinimalSecurityToken, which causes my security attribute to reject the request (as it should). However, when I'm stepping through, I can see that IMinimalSecurityToken is already registered (i.e. x.IsRegistered<IminimalSecurityToken() returns true). If I try to return the resolved IMinimalSecurityToken if it's already registered, I get a circular reference error.
In addition, attempting to use InstancePerApiRequest while registering results in a "No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested" error.
I'm not entirely clear on why this delegate gets called multiple times, nor why in one of the calls it doesn't have an HttpRequestMethod.
What am I doing wrong that is causing multiple executions of the delegate (assuming that is in fact a problem), and how do I fix this so that I can properly inject a valid IMinimalSecurityToken into my SecurityAttribute (via property injection, but that part is working, it's just that what gets injected isn't valid).
So this problem turned out to be caused by trying to "convert" the Ninject code over to Autofac.
I was able to use Autofac's capabilities to inject Web API filters to wire up the security attribute, which allowed me to inject the required IMinimalSecurityToken in the constructor, rather than the rather hacky way I had to work around Ninject's limitations in that area:
container.RegisterType<SecurityTokenAuthorization>().As<IAutofacAuthorizationFilter>().InstancePerApiRequest();
container.RegisterType<SecurityTokenAuthorization>().AsWebApiAuthorizationFilterFor<DocumentController>().InstancePerApiRequest();
I've got other issues now, but they are sufficiently different that they'll need a new question assuming I'm not able to solve them.

Categories