Refresh Token using Polly with Named Client - c#

I have a policy that looks like this
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(resp => resp.StatusCode == HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (resp, timeSpan, context) =>
{
// not sure what to put here
});
Then I have a named client that looks like this
services.AddHttpClient("MyClient", client =>
{
client.BaseAddress = new Uri("http://some-url.com");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
client.Timeout = 30000;
})
.AddPolicyHandler(retryPolicy);
I need to refresh the bearer token on the http client in the event I receive a 401. So in a perfect world the following code would do exactly what I'm trying to accomplish
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(resp => resp.StatusCode == HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (resp, timeSpan, context) =>
{
var newToken = GetNewToken();
//httpClient doesn't exists here so I need to grab it some how
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", newToken);
});
I have read the following articles:
Re-establishing authentication using Retry
Refresh Token using Polly with Typed Client
retry-to-refresh-authorization
and a couple others as well. However, they all seem use policy.ExecuteAsync() which I don't want to use because then I would have to change all the HttpClient calls throughout my solution. I'm trying to find a way to simply add this functionality to every request by only changing code in the StartUp.cs.

TL;DR: You need to define a communication protocol between a RetryPolicy, a DelegatingHandler and a TokenService.
In case of Typed Clients you can explicitly call the ExecuteAsync and use the Context to exchange data between the to-be-decorated method and the onRetry(Async) delegate.
This trick can't be used in a named client situation. What you need to do instead:
Separate out the Token management into a dedicated service
Use a DelegatingHandler to intercept the HttpClient's communication
This sequence diagram depicts the communication between the different components
Token Service
The DTO
public class Token
{
public string Scheme { get; set; }
public string AccessToken { get; set; }
}
The interface
public interface ITokenService
{
Token GetToken();
Task RefreshToken();
}
The dummy implementation
public class TokenService : ITokenService
{
private DateTime lastRefreshed = DateTime.UtcNow;
public Token GetToken()
=> new Token { Scheme = "Bearer", AccessToken = lastRefreshed.ToString("HH:mm:ss")};
public Task RefreshToken()
{
lastRefreshed = DateTime.UtcNow;
return Task.CompletedTask;
}
}
The registration into the DI as Singleton
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITokenService, TokenService>();
...
}
Delegating Handler
The custom exception
public class OutdatedTokenException : Exception
{
}
The handler (interceptor)
public class TokenFreshnessHandler : DelegatingHandler
{
private readonly ITokenService tokenService;
public TokenFreshnessHandler(ITokenService service)
{
tokenService = service;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = tokenService.GetToken();
request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new OutdatedTokenException();
}
return response;
}
}
It retrieves the current token from the TokenService
It sets the authorization header
It executes the base method
It checks the response's status
If 401 then it throws the custom exception
If other than 401 then it returns with the response
The registration into the DI as Transient
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITokenService, TokenService>();
services.AddTransient<TokenFreshnessHandler>();
...
}
Retry Policy
The policy definition
public IAsyncPolicy<HttpResponseMessage> GetTokenRefresher(IServiceProvider provider)
{
return Policy<HttpResponseMessage>
.Handle<OutdatedTokenException>()
.RetryAsync(async (_, __) => await provider.GetRequiredService<ITokenService>().RefreshToken());
}
It receives an IServiceProvider to be able to access the TokenService
It performs a single retry if an OutdatedTokenException was thrown
Inside the onRetryAsync delegate it calls the TokenService's RefreshToken method
Putting all things together
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITokenService, TokenService>();
services.AddTransient<TokenFreshnessHandler>();
services.AddHttpClient("TestClient")
.AddPolicyHandler((provider, _) => GetTokenRefresher(provider))
.AddHttpMessageHandler<TokenFreshnessHandler>();
...
}
Please bear in mind that the ordering of AddPolicyHandler and AddHttpMessageHandler matters
If you would call the AddHttpMessageHandler first and then the AddPolicyHandler in that case your retry would not be triggered

This post contains an alternative version of my previously suggested solution.
I'm posting this as a separate answer (rather than editing the previous one) because both solutions are viable and the other post is already a lengthy one.
Why do we need an alternative version?
Because the TokenFreshnessHandler has too much responsibility whereas the Retry policy has too few.
If you look at the SendAsync method overridden implementation then you can see that it perform some operation on the request and on the response as well.
If we could make a separation where
the handler deals only with the request
and policy make its assessment on the response
then we would end up with a much cleaner solution (IMHO).
How can we achieve this separation?
If we could use the Polly's Context as an intermediate storage between the retry attempts then we were able to do this separation. Fortunately the Microsoft.Extensions.Http.Polly package defines two extension methods against the HttpRequestMessage:
SetPolicyExecutionContext
GetPolicyExecutionContext
These are under-documented features. On the docs.microsoft I could not even find the related pages. I have only found them under the dotnet-api-docs repo.
These can be useful if we know that the AddPolicyHandler attaches a new Context to the request only if it did not have one already. Unfortunately, this is yet again not documented, so it is an implementation detail which might change in the future. But currently we can rely on this.
How will this change the protocol?
As you see only difference here is the usage of the Context.
How should we change the handler?
public class TokenRetrievalHandler : DelegatingHandler
{
private readonly ITokenService tokenService;
private const string TokenRetrieval = nameof(TokenRetrieval);
private const string TokenKey = nameof(TokenKey);
public TokenRetrievalHandler(ITokenService service)
{
tokenService = service;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var context = request.GetPolicyExecutionContext();
if(context.Count == 0)
{
context = new Context(TokenRetrieval, new Dictionary<string, object> { { TokenKey, tokenService.GetToken() } });
request.SetPolicyExecutionContext(context);
}
var token = (Token)context[TokenKey];
request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
I've changed the name of the handler since its responsibilities have changed
Now, the handler's implementation only cares about the request (and does not care about the response)
As it was said previously: the PolicyHttpMessageHandler creates a new Context if there wasn't any
Because of this the GetPolicyExecutionContext does not return null (even for the very first attempt) rather than a Context with an empty context data collection (context.Count == 0)
How should we change the policy?
public IAsyncPolicy<HttpResponseMessage> GetTokenRefresher(IServiceProvider provider, HttpRequestMessage request)
{
return Policy<HttpResponseMessage>
.HandleResult(response => response.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(async (_, __) =>
{
await provider.GetRequiredService<ITokenService>().RefreshToken();
request.SetPolicyExecutionContext(new Context());
});
}
Rather than triggering the policy for a custom exception, now it triggers in case of 401 response's status code
The onRetryAsync has been modified in the way that it clears the attached context of the request
The registration code should be adjusted as well
services.AddHttpClient("TestClient")
.AddPolicyHandler((sp, request) => GetTokenRefresher(sp, request))
.AddHttpMessageHandler<TokenRetrievalHandler>()
Now, we should pass to the GetTokenRefresher method not just the IServiceProvider but also HttpRequestMessage as well
Which solution should I use?
This solution offers nicer separation but it relies on an implementation detail
The other solution makes the handler smart whereas the policy dumb

Related

Polly policy not working using "AddPolicyHandler"

I have an application that makes a request for an authenticated service, where it is necessary to pass the access_token.
My idea is to use Polly to retry if the access_token is expired.
I'm using Refit (v5.1.67) and Polly (v7.2.1) in a .NET Core 3.1 application.
The services are registered as follows:
services.AddTransient<ExampleDelegatingHandler>();
IAsyncPolicy<HttpResponseMessage> retryPolicy = Policy<HttpResponseMessage>
.Handle<ApiException>()
.RetryAsync(1, (response, retryCount) =>
{
System.Diagnostics.Debug.WriteLine($"Polly Retry => Count: {retryCount}");
});
services.AddRefitClient<TwitterApi>()
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("https://api.twitter.com/");
})
.AddHttpMessageHandler<ExampleDelegatingHandler>()
.AddPolicyHandler((sp, req) =>
{
//this policy does not works, because the exception is not catched on
//"Microsoft.Extensions.Http.PolicyHttpMessageHandler" (DelegatingHandler)
return retryPolicy;
});
public interface TwitterApi
{
[Get("/2/users")]
Task<string> GetUsers();
}
public class ExampleDelegatingHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await base.SendAsync(request, cancellationToken);
}
catch (Exception)
{
//Why do not catch the exception?
throw;
}
}
}
The retry policy is not working!
Analyzing the problem, I realized that the exception is not being caught inside the HttpClient's DelegatingHandler. Since the AddPolicyHandler statement is generating a DelegatingHandler (PolicyHttpMessageHandler) to execute the policy and the exception is not caught there, the policy never executes. I realized that the problem only occurs in asynchronous scenarios, where the request can be sent. In synchronous scenarios it works (example: timeout).
Why the exception is not caught inside DelegatingHandler??
I am attaching an example project simulating a Twitter call.
https://www.dropbox.com/s/q1797rq1pbjvcls/ConsoleApp2.zip?dl=0
External references:
https://github.com/reactiveui/refit#using-httpclientfactory
https://www.hanselman.com/blog/UsingASPNETCore21sHttpClientFactoryWithRefitsRESTLibrary.aspx
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1
I had an issue involving .NET 5 >= with Polly and HttpClient, which the compiler showed: HttpClientBuilder does not contain a definition for AddPolicyHandler.
I could fix it when I changed the Nuget PackagePolly.Extensions.Http to Microsoft.Extensions.Http.Polly, I know that isn't the same situation reported here but it might be useful for other people who have come here to find this answer, like me.
TL;DR: The ordering of AddPolicyHandler and AddHttpMessageHandler does matter.
I've recreated the problem with HttpClient (so without Refit).
Typed HttpClient for testing
public interface ITestClient
{
Task<string> Get();
}
public class TestClient: ITestClient
{
private readonly HttpClient client;
public TestClient(HttpClient client)
{
this.client = client;
}
public async Task<string> Get()
{
var resp = await client.GetAsync("http://not-existing.site");
return "Finished";
}
}
Controller for testing
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
private readonly ITestClient client;
public TestController(ITestClient client)
{
this.client = client;
}
[HttpGet]
public async Task<string> Get()
{
return await client.Get();
}
}
DelegateHandler for testing
public class TestHandler: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await base.SendAsync(request, cancellationToken);
}
catch (System.Exception ex)
{
_ = ex;
throw;
}
}
}
Ordering #1 - Handler, Policy
Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<TestHandler>();
services.AddHttpClient<ITestClient, TestClient>()
.AddHttpMessageHandler<TestHandler>() //Handler first
.AddPolicyHandler(RetryPolicy()); //Policy second
}
private IAsyncPolicy<HttpResponseMessage> RetryPolicy()
=> Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.RetryAsync(1, (resp, count) =>
{
Console.WriteLine(resp.Exception);
});
Execution order
TestController's Get
TestClient's Get
TestHandler's SendAsync's try
RetryPolicy's onRetry
TestHandler's SendAsync's catch
TestController's Get fails with HttpRequestException (inner: SocketException)
So, here the retry policy does not fired.
Ordering #2 - Policy, Handler
Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<TestHandler>();
services.AddHttpClient<ITestClient, TestClient>()
.AddPolicyHandler(RetryPolicy()) //Policy first
.AddHttpMessageHandler<TestHandler>(); //Handler second
}
private IAsyncPolicy<HttpResponseMessage> RetryPolicy()
=> Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.RetryAsync(1, (resp, count) =>
{
Console.WriteLine(resp.Exception);
});
Execution order
TestController's Get
TestClient's Get
TestHandler's SendAsync's try
TestHandler's SendAsync's catch
RetryPolicy's onRetry
TestHandler's SendAsync's try
TestHandler's SendAsync's catch
TestController's Get fails with HttpRequestException (inner: SocketException)
So, here the retry policy has been fired.
1. Why
At the time policies and delegating handlers are executed, a failed HTTP response is not an exception yet. It's just an instance of HttpResponseMessage with an unsuccessful status. Refit converts this status into an exception as the very last step in the request-response processing.
2. Order
As correctly noted in Peter Csala's answer, order matters. When a request is made:
Refit serializes the parameters into an HttpRequestMessage and passes it to HttpClient
HttpClient does initial preparations
HttpClient runs the request messages through the handlers and policies in the order they were added to the client
The resulting message is sent to the server
Server's response is converted to a HttpResponseMessage object
This object bubbles up through the same sequence of handlers and policies but in reverse order
HttpClient does final processing and returns the result to Refit
Refit converts any errors into ApiExceptions
Therefore a retry policy will re-run everything that was added after it, but whatever was before it will be executed only once.
So if you want your access_token to be re-generated on every retry, the delegating handler that creates the token must be registered after the retry policy.
3. How
The easiest way to retry on HTTP failures is to use HttpPolicyExtensions.HandleTransientHttpError() from Polly.Extensions.Http. Otherwise you'd have to check for all the failure HTTP status codes yourself. The benefit of HandleTransientHttpError is that it only retries on failures which makes sense to retry, like 500 or socket errors. On the other hand it will not retry a 404 for instance, because the resource is not there and is unlikely to reappear if we try again.
I think if we change the policy
IAsyncPolicy<HttpResponseMessage> retryPolicy = Policy<HttpResponseMessage>
.Handle<ApiException>()
.RetryAsync(1, (response, retryCount) =>
{
System.Diagnostics.Debug.WriteLine($"Polly Retry => Count: {retryCount}");
});
to
.HandleResult(x => !x.IsSuccessStatusCode)
or
.HandleResult(x => _retryStatusCodes.Contains(x.StatusCode))
...
private static readonly ISet<HttpStatusCode> _retryStatusCodes = new HashSet<HttpStatusCode>
{
HttpStatusCode.RequestTimeout,
HttpStatusCode.BadGateway,
HttpStatusCode.ServiceUnavailable,
HttpStatusCode.GatewayTimeout,
};
then it should work.
IAsyncPolicy<HttpResponseMessage> retryPolicy = Policy<HttpResponseMessage>
.HandleResult(x => _retryStatusCodes.Contains(x.StatusCode))
.RetryAsync(1, (response, retryCount) =>
{
System.Diagnostics.Debug.WriteLine($"Polly Retry => Count: {retryCount}");
});
Maybe Refit checks status code and throws ApiException on some later stage

Multiple httpclient instances with same implementation in dotnet core

I would like to call a third party API which provided us two different authorization token values. So we are getting two different sets of results back via invoking the same endpoint URL.
EndpointUrl: https://mail.yahoo.com/
Authorization: Token User123
//Do something with the response for User123
Authorization: Token User345
//Do something with the response for User345
In my client service, my wrapper function should invoke this API by calling it twice with different token values. Get the result and merge it.
Here is my service.
public class MailService : IMailService
{
private readonly HttpClient _httpClient;
public MailService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<UserResponse> GetUserResponse()
{
var uri = new Uri(_httpClient.BaseAddress.AbsoluteUri + "/user-data/");
var response = await _httpClient.GetAsync(uri);
return response;
}
}
I was using Typed Client:
services.AddHttpClient<IMailService,MailService>(client =>
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", "User123");
client.BaseAddress = new Uri("https://mail.yahoo.com/");
})
Problem Statement:
What is the best way to retrieve the results? I am not sure if I should create two separate instances of HttpClient? I would like to avoid repetitive code if I go with two different classes containing their own HttpClient.
Maybe my solution lies somewhere in Named Client. I just don't know how to implement that gracefully.
Any help would be appreciated.
You can delegate adding the token header later for each message. Remove the auth header from Startup and add Http message handler (Create a new class "AuthHandler").
builder.Services.AddScoped<AuthHandler>();
services.AddHttpClient<IMailService,MailService>(client => {
client.BaseAddress = new Uri("https://mail.yahoo.com/");
})
.AddHttpMessageHandler<AuthHandler>();
In the AuthHandler, you can add the logic to retrieve and set the auth header.
The override SendAsync method will be called every time a http call is made. Below is a sample code, you can modify as per your logic/requirements:
public class AuthHandler : DelegatingHandler
{
private readonly AppSettings _appSettings;
private static string? _accessToken;
public AuthHandler(IOptions<AppSettings> options)
{
_appSettings = options.Value;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = GetToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return base.SendAsync(request, cancellationToken);
}
private string GetToken()
{
if (_accessToken != null)
{
return _accessToken;
}
_accessToken = "123";// Get your token
return _accessToken;
}
}
What is the best way to retrieve the results?
Named clients are one solution, if you are certain you will always have two authentication headers. That seems very odd to me, though.
SendAsync is another solution. By calling SendAsync instead of GetAsync, you can provide an HttpRequestMessage that can have custom headers. GetAsync is essentially a wrapper around SendAsync for convenience, and you can use the lower-level SendAsync since you need more customization.
I am going to answer, and this is more-or-less opinion based. I like my DI services to be decoupled from everything else in my project. Basically you are putting a configuration to another service in your startup.cs. I like to keep all that stuff in the service that consumes the HttpClient.
So when I inject the IHttpClientFactory, I do it by simply calling:
services.AddHttpClient();
And move on.
Now, in your MailService, you would inject it as so:
public class MailService : IMailService
{
// or store this in your applications external configuration
const Uri BaseUri = new Uri("https://mail.yahoo.com/");
const string UserDataPath = "/user-data/";
private readonly IHttpClientFactory _httpClientFactory;
public MailService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<UserResponse> GetUserResponse(string token)
{
var client = _httpClientFactory.CreateClient();
var uri = new UriBuilder(BaseUri){ Path = UserDataPath }.Uri;
using (var msg = new HttpRequestMessage(HttpMethod.Get, uri))
{
msg.Headers.Authorization = new AuthenticationHeaderValue("Token", token);
using (var resp = await _client.SendAsync(msg).ConfigureAwait(false))
{
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsAsync<UserResponse>().ConfigureAwait(false);
}
}
}
}
Keep in mind that you should use the HttpRequestMessage pattern and not modify any Default... properties of an HttpClient. It could be in use some where else in your application at that time.
Using HttpRequestMessage ensures you get a fresh set of headers that no one else can modify. To sum it up: HttpClient.Headers are not thread-safe.
ETA:
Now that I think about it... that really is your question. You want to use two different headers using one HttpClient. It won't work in the scenario you presented. You would have to have two HttpClient with their own distinct headers.
So, if you don't want to do it the old-school way I presented, you should consider "Named" clients... each configuration has a different name.

How to create named client after startup?

For my application i need to make a named client for HttpRequests. I can create a named client in Startup. And to access it i inject an "IHttpClientFactory" and create a client from that. But the client needs to have an access token as an authorization header, and i cannot create the token in Startup. Therefor i need a way to create a named client outside of the Startup class. i have already tried injecting "IServiceCollection" into a controller. But this does not work.
Or is there maybe a way to edit a named client after it is already created in startup?
A similar solution to the one posted by #Ruben-J is to create a custom HttpMessageHandler which assigns an authorization header to requests made through the HttpClient at request-time.
You can create a custom HttpMessageHandler that can be assigned to a named HttpClient in Startup like so:
public class YourHttpMessageHandler : DelegatingHandler
{
private readonly IYourTokenProviderService _yourTokenProviderService;
public YourHttpMessageHandler(IYourTokenProviderService yourTokenProviderService)
: base()
{
_yourTokenProviderService = yourTokenProviderService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = SendAsyncWithAuthToken(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
await _yourTokenProviderService.RefreshTokenAsync();
response = SendAsyncWithAuthToken(request, cancellationToken);
}
return response;
}
private async Task<HttpResponseMessage> SendWithAuthTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _yourTokenProviderService.Token);
return await base.SendAsync(request, cancellationToken);
}
}
You then configure your services and named HttpClient in Startup:
public virtual void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<IYourTokenProviderService, YourTokenProviderService>();
services.AddTransient<YourHttpMessageHandler>();
services.AddHttpClient<IYourNamedHttpClient, YourNamedHttpClient>()
.AddHttpMessageHandler<YourHttpMessageHandler>();
...
}
Its worth noting that the current implementation of Polly's AddPolicyHandler is also adding its own DelegatingHandler.
For more background see the Microsoft documentation on adding DelegatingHandler's. Here is also great series of articles from Steve Gordon.
You could use Polly to add a policy handler to your client. You can then add logic if a request returns a 401 Unauthorized. So for example get your service that uses the client to refresh a bearer token and also set it for the current request. This is just a quick solution and maybe there are more elegant solutions. But this will also come in handy if your token expires. Cause then it will be refreshed automatically.
services.AddHttpClient("YourClient")
.AddPolicyHandler((provider, request) =>
{
return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(1, async (response, retryCount, context) =>
{
var service = provider.GetRequiredService<IYourService>();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await service.RefreshToken());
});
});

Set a default Polly policy with Flurl

I'm currently using Polly and Flurl together, but I have a common retry policy that I have to add to every request. I notice that Polly allows you to set a default using AddPolicyHandler(...) but this requires an IHttpClientBuilder and I can't see any way of getting hold of this from Flurl.
I thought overloading DefaultHttpClientFactory might be the way to go, but that only gives me access to the HttpClient, not the IHttpClientBuilder.
I know I could make my own HttpClients and pass them into Flurl, but I'd rather avoid that if I can as I'd like Flurl to manage their lifecycle.
Is there currently a way of doing what I want to do?
Great question. Flurl gives you all the necessary hooks to do this. First define a DelegatingHandler that takes a Polly policy:
public class PollyHandler : DelegatingHandler
{
private readonly IAsyncPolicy<HttpResponseMessage> _policy;
public PollyHandler(IAsyncPolicy<HttpResponseMessage> policy) {
_policy = policy;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _policy.ExecuteAsync(ct => base.SendAsync(request, ct), cancellationToken);
}
}
Then create a custom IHttpClientFactory that returns your custom handler with the default handler as its InnerHandler:
public class PollyFactory : DefaultHttpClientFactory
{
private readonly IAsyncPolicy<HttpResponseMessage> _policy;
public PollyFactory(IAsyncPolicy<HttpResponseMessage> policy) {
_policy = policy;
}
public override HttpMessageHandler CreateMessageHandler() {
return new PollyHandler(_policy) {
InnerHandler = base.CreateMessageHandler()
};
}
}
Finally, on app startup, define your policy and register it with Flurl:
var policy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.RetryAsync(5);
FlurlHttp.Configure(settings => settings.HttpClientFactory = new PollyFactory(policy));
One important note is that this approach will not work with a policy that handles FlurlHttpException. That's because you're intercepting calls at the HttpMessageHandler level here. Flurl converts responses and errors to FlurlHttpExceptions higher up the stack, so those won't get trapped/retried with this approach. The policy in the example above traps HttpRequestException and HttpResponseMessage (with non-2XX status codes), which will work.

Mediator pipline behaviors registration

I have a asp.net core web api project with Mediatr 3.0.1 and structureMap.Microsoft.DependencyInjection 1.4.0.
I would like to use the pre and post-processing behavior.
The IPipelineBehavior works as expected but thoese 2 don't:
public class PostProcessingBehavior<TRequest, TResponse> : IRequestPostProcessor<TRequest, TResponse>
{
public Task Process(TRequest request, TResponse response)
{
LogTo.Info("Post processing: All Done");
return Task.FromResult(0);
}
}
public class PreProcessingBehavior<TRequest>: IRequestPreProcessor<TRequest>
{
public Task Process(TRequest request)
{
// add validators here
LogTo.Info("Pipline preprocessing happens");
return Task.FromResult(0);
}
}
Container registration:
private IServiceProvider ConfigureIoC(IServiceCollection services)
{
var container = new Container();
container.Configure(cfg =>
{
cfg.Scan(scanner =>
{
scanner.AssemblyContainingType(typeof(Startup));
scanner.AssemblyContainingType(typeof(CustomerGetHandler));
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<>)); // Handlers with no response
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>)); // Handlers with a response
scanner.ConnectImplementationsToTypesClosing(typeof(IAsyncRequestHandler<>)); // Async handlers with no response
scanner.ConnectImplementationsToTypesClosing(typeof(IAsyncRequestHandler<,>)); // Async Handlers with a response
scanner.ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>));
scanner.ConnectImplementationsToTypesClosing(typeof(IAsyncNotificationHandler<>));
scanner.WithDefaultConventions();
});
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(PreProcessingBehavior<>));
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(LoggingBehavior<,>));
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(PostProcessingBehavior<,>));
cfg.For<SingleInstanceFactory>().Use<SingleInstanceFactory>(ctx => t => ctx.GetInstance(t)).ContainerScoped();
cfg.For<MultiInstanceFactory>().Use<MultiInstanceFactory>(ctx => t => ctx.GetAllInstances(t)).ContainerScoped();
cfg.For<IMediator>().Use<Mediator>();
cfg.Populate(services);
});
return container.GetInstance<IServiceProvider>();
}
The error says:
ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: instance 'PostProcessingBehavior' with ReturnType PostProcessingBehavior cannot be cast to IPipelineBehavior
this is from the example:
https://github.com/jbogard/MediatR/blob/master/samples/MediatR.Examples.StructureMap/Program.cs
Looking at the example you have linked to you seem to have confused the interfaces.
You have implemented IRequestPostProcessor & IRequestPreProcessor not an IPipelineBehavior.
So you should be registering to the interfaces you are implementing:
cfg.For(typeof(IRequestPreProcessor<>)).Add(typeof(PreProcessingBehavior<>));
cfg.For(typeof(IRequestPostProcessor<,>)).Add(typeof(PostProcessingBehavior<,>));
From reading the documentation, you may also need to register the following
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPreProcessorBehavior<,>));
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPostProcessorBehavior<,>));

Categories