我正在为我的 .NET Core Web Api 实现弹性,并且我需要一种使用
ResilienceContext
传递值的方法,但当我调试时,args.Context.Properties
始终为空。我不知道我错过了什么。
我正在使用
Microsoft.Extensions.Http.Resilience
使用 Polly
这是我的代码:
public static class ResilienceKeys
{
public static readonly ResiliencePropertyKey<string> LocationId = new("location-id");
}
// WordPressService.cs
public class WordPressService : IWordPressService
{
private readonly HttpClient _httpClient;
private readonly ResiliencePipelineProvider<string> _pipelineProvider;
public WordPressService(HttpClient httpClient, ResiliencePipelineProvider<string> pipelineProvider)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.example.com/");
_pipelineProvider = pipelineProvider;
}
public async Task<string> GetBannersByLocationAsync(int locationId, CancellationToken cancellationToken)
{
var pipeline = _pipelineProvider.GetPipeline<HttpResponseMessage>("my-pipeline");
var context = ResilienceContextPool.Shared.Get(cancellationToken);
context.Properties.Set(ResilienceKeys.LocationId, locationId.ToString());
var response = await pipeline.ExecuteAsync<HttpResponseMessage, ResilienceContext>(
async (context, cancellationToken) =>
{
// Simulate an error for testing purposes
await Task.Delay(100, cancellationToken);
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
return response;
},
context,
cancellationToken);
return await response.Content.ReadAsStringAsync(cancellationToken);
}
}
我还使用依赖注入来添加 ResiliencePipeline,并使用
RetryStrategy
和 FallbackStrategy
从数据库获取内容,以防 WordPress 服务器没有响应
// Program.cs
builder.Services.AddResiliencePipeline<string, HttpResponseMessage>("my-pipeline", (pipelineBuilder, context) =>
{
var predicateBuilder = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.HandleResult(r => r.StatusCode >= HttpStatusCode.InternalServerError);
pipelineBuilder
.AddFallback(new FallbackStrategyOptions<HttpResponseMessage>()
{
ShouldHandle = predicateBuilder,
FallbackAction = async (args) =>
{
if (args.Context.Properties.TryGetValue(ResilienceKeys.LocationId, out var data))
{
Console.WriteLine("FallbackAction, Location Id: {0}", data);
}
var fallbackResponse = new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("Fallback content")
};
return Outcome.FromResult(fallbackResponse);
}
})
.AddRetry(new RetryStrategyOptions<HttpResponseMessage>()
{
ShouldHandle = predicateBuilder,
Delay = TimeSpan.FromSeconds(2),
MaxRetryAttempts = 2,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
});
});
简短的回答是您使用了错误的
ExecuteAsync
重载。你应该使用这个:
var response = await pipeline.ExecuteAsync<HttpResponseMessage>(
async ctx =>
{
// Simulate an error for testing purposes
await Task.Delay(100, ctx.CancellationToken);
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
},
context);
超载,你所使用的,没有利用
Context
。如果你看到方法签名,那就很清楚了。
public async ValueTask<TResult> ExecuteAsync<TResult, TState>(
Func<TState, CancellationToken, ValueTask<TResult>> callback,
TState state,
CancellationToken cancellationToken = default)
您已将
Context
作为 state
对象传递。 这里我们记录了 Context
和 State
之间有什么区别。
目前不存在预期会出现
CancellationToken
和 Context
的此类过载。 但你根本不需要。
每当您从
Context
请求 ResilienceContextPool
时,传递的 CancellationToken
就会附加到检索到的 Context
。如果您查看建议解决方案的代码片段,您可以发现 CancellationToken
是通过 Context
(ctx.CancellationToken
) 访问的。
因此,您应该使用以下重载:
public async ValueTask<TResult> ExecuteAsync<TResult>(
Func<ResilienceContext, ValueTask<TResult>> callback,
ResilienceContext context)
作为旁注,请不要忘记归还借用的弹性上下文到池中:
ResilienceContextPool.Shared.Return(context);