Skip to content

Commit 18002be

Browse files
Add methods allowing retrieval of HttpRequestMessage from ResilienceContext (#5460)
Fixes #4957 - Adds extension methods allowing retrieval of the request message from resilience context - Replaces usage of ResilienceKeys.RequestMessage variable with corresponding Get/Set methods
1 parent 06e66b7 commit 18002be

File tree

10 files changed

+158
-15
lines changed

10 files changed

+158
-15
lines changed

src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/RequestMessageSnapshotStrategy.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState
2020
ResilienceContext context,
2121
TState state)
2222
{
23-
if (!context.Properties.TryGetValue(ResilienceKeys.RequestMessage, out var request) || request is null)
23+
HttpRequestMessage? request = context.GetRequestMessage();
24+
25+
if (request is null)
2426
{
2527
Throw.InvalidOperationException("The HTTP request message was not found in the resilience context.");
2628
}

src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/ResilienceHttpClientBuilderExtensions.Hedging.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt
9494
requestMessage.SetResilienceContext(args.ActionContext);
9595

9696
// replace the request message
97-
args.ActionContext.Properties.Set(ResilienceKeys.RequestMessage, requestMessage);
97+
args.ActionContext.SetRequestMessage(requestMessage);
9898

9999
if (args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RoutingStrategy, out var routingPipeline))
100100
{

src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ResilienceKeys.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Http.Resilience.Internal;
1010

1111
internal static class ResilienceKeys
1212
{
13-
public static readonly ResiliencePropertyKey<HttpRequestMessage> RequestMessage = new("Resilience.Http.RequestMessage");
13+
public static readonly ResiliencePropertyKey<HttpRequestMessage?> RequestMessage = new("Resilience.Http.RequestMessage");
1414

1515
public static readonly ResiliencePropertyKey<RequestRoutingStrategy> RoutingStrategy = new("Resilience.Http.RequestRoutingStrategy");
1616

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Net.Http;
7+
using Microsoft.Extensions.Http.Resilience.Internal;
8+
using Microsoft.Shared.DiagnosticIds;
9+
using Microsoft.Shared.Diagnostics;
10+
using Polly;
11+
12+
namespace Polly;
13+
14+
/// <summary>
15+
/// Provides utility methods for working with <see cref="ResilienceContext"/>.
16+
/// </summary>
17+
[Experimental(diagnosticId: DiagnosticIds.Experiments.Resilience, UrlFormat = DiagnosticIds.UrlFormat)]
18+
public static class HttpResilienceContextExtensions
19+
{
20+
/// <summary>
21+
/// Gets the request message from the <see cref="ResilienceContext"/>.
22+
/// </summary>
23+
/// <param name="context">The resilience context.</param>
24+
/// <returns>
25+
/// The request message.
26+
/// If the request message is not present in the <see cref="ResilienceContext"/> the method returns <see langword="null"/>.
27+
/// </returns>
28+
/// <exception cref="ArgumentNullException"><paramref name="context"/> is <see langword="null"/>.</exception>
29+
public static HttpRequestMessage? GetRequestMessage(this ResilienceContext context)
30+
{
31+
_ = Throw.IfNull(context);
32+
return context.Properties.GetValue(ResilienceKeys.RequestMessage, default);
33+
}
34+
35+
/// <summary>
36+
/// Sets the request message on the <see cref="ResilienceContext"/>.
37+
/// </summary>
38+
/// <param name="context">The resilience context.</param>
39+
/// <param name="requestMessage">The request message.</param>
40+
/// <exception cref="ArgumentNullException"><paramref name="context"/> is <see langword="null"/>.</exception>
41+
public static void SetRequestMessage(this ResilienceContext context, HttpRequestMessage? requestMessage)
42+
{
43+
_ = Throw.IfNull(context);
44+
context.Properties.Set(ResilienceKeys.RequestMessage, requestMessage);
45+
}
46+
}

src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandler.cs

+3-6
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
5858

5959
ResilienceContext context = GetOrSetResilienceContext(request, cancellationToken, out bool created);
6060
TrySetRequestMetadata(context, request);
61-
SetRequestMessage(context, request);
61+
context.SetRequestMessage(request);
6262

6363
try
6464
{
@@ -117,7 +117,7 @@ protected override HttpResponseMessage Send(HttpRequestMessage request, Cancella
117117

118118
ResilienceContext context = GetOrSetResilienceContext(request, cancellationToken, out bool created);
119119
TrySetRequestMetadata(context, request);
120-
SetRequestMessage(context, request);
120+
context.SetRequestMessage(request);
121121

122122
try
123123
{
@@ -165,11 +165,8 @@ private static void TrySetRequestMetadata(ResilienceContext context, HttpRequest
165165
}
166166
}
167167

168-
private static void SetRequestMessage(ResilienceContext context, HttpRequestMessage request)
169-
=> context.Properties.Set(ResilienceKeys.RequestMessage, request);
170-
171168
private static HttpRequestMessage GetRequestMessage(ResilienceContext context, HttpRequestMessage request)
172-
=> context.Properties.GetValue(ResilienceKeys.RequestMessage, request);
169+
=> context.GetRequestMessage() ?? request;
173170

174171
private static void RestoreResilienceContext(ResilienceContext context, HttpRequestMessage request, bool created)
175172
{

src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/RoutingResilienceStrategy.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Net.Http;
56
using System.Threading.Tasks;
67
using Microsoft.Extensions.Http.Resilience.Internal;
78
using Microsoft.Shared.Diagnostics;
@@ -26,7 +27,9 @@ protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState
2627
ResilienceContext context,
2728
TState state)
2829
{
29-
if (!context.Properties.TryGetValue(ResilienceKeys.RequestMessage, out var request))
30+
HttpRequestMessage? request = context.GetRequestMessage();
31+
32+
if (request is null)
3033
{
3134
Throw.InvalidOperationException("The HTTP request message was not found in the resilience context.");
3235
}

test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public async Task ExecuteAsync_EnsureSnapshotAttached()
2020
var strategy = Create();
2121
var context = ResilienceContextPool.Shared.Get();
2222
using var request = new HttpRequestMessage();
23-
context.Properties.Set(ResilienceKeys.RequestMessage, request);
23+
context.SetRequestMessage(request);
2424

2525
using var response = await strategy.ExecuteAsync(
2626
context =>
@@ -39,5 +39,15 @@ public void ExecuteAsync_RequestMessageNotFound_Throws()
3939
strategy.Invoking(s => s.Execute(() => { })).Should().Throw<InvalidOperationException>();
4040
}
4141

42+
[Fact]
43+
public void ExecuteAsync_RequestMessageIsNull_Throws()
44+
{
45+
var strategy = Create();
46+
var context = ResilienceContextPool.Shared.Get();
47+
context.SetRequestMessage(null);
48+
49+
strategy.Invoking(s => s.Execute(_ => { }, context)).Should().Throw<InvalidOperationException>();
50+
}
51+
4252
private static ResiliencePipeline Create() => new ResiliencePipelineBuilder().AddStrategy(_ => new RequestMessageSnapshotStrategy(), Mock.Of<ResilienceStrategyOptions>()).Build();
4353
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Net.Http;
6+
using Microsoft.Extensions.Http.Resilience.Internal;
7+
using Polly;
8+
using Xunit;
9+
10+
namespace Microsoft.Extensions.Http.Resilience.Test.Resilience;
11+
12+
public class HttpResilienceContextExtensionsTests
13+
{
14+
[Fact]
15+
public void GetRequestMessage_ResilienceContextIsNull_Throws()
16+
{
17+
ResilienceContext context = null!;
18+
Assert.Throws<ArgumentNullException>(context.GetRequestMessage);
19+
}
20+
21+
[Fact]
22+
public void GetRequestMessage_RequestMessageIsMissing_ReturnsNull()
23+
{
24+
var context = ResilienceContextPool.Shared.Get();
25+
26+
Assert.Null(context.GetRequestMessage());
27+
}
28+
29+
[Fact]
30+
public void GetRequestMessage_RequestMessageIsNull_ReturnsNull()
31+
{
32+
var context = ResilienceContextPool.Shared.Get();
33+
context.Properties.Set(ResilienceKeys.RequestMessage, null);
34+
35+
Assert.Null(context.GetRequestMessage());
36+
}
37+
38+
[Fact]
39+
public void GetRequestMessage_RequestMessageIsPresent_ReturnsRequestMessage()
40+
{
41+
var context = ResilienceContextPool.Shared.Get();
42+
using var request = new HttpRequestMessage();
43+
context.Properties.Set(ResilienceKeys.RequestMessage, request);
44+
45+
Assert.Same(request, context.GetRequestMessage());
46+
}
47+
48+
[Fact]
49+
public void SetRequestMessage_ResilienceContextIsNull_Throws()
50+
{
51+
ResilienceContext context = null!;
52+
using var request = new HttpRequestMessage();
53+
54+
Assert.Throws<ArgumentNullException>(() => context.SetRequestMessage(request));
55+
}
56+
57+
[Fact]
58+
public void SetRequestMessage_RequestMessageIsNull_SetsNullRequestMessage()
59+
{
60+
var context = ResilienceContextPool.Shared.Get();
61+
context.SetRequestMessage(null);
62+
63+
Assert.True(context.Properties.TryGetValue(ResilienceKeys.RequestMessage, out HttpRequestMessage? request));
64+
Assert.Null(request);
65+
}
66+
67+
[Fact]
68+
public void SetRequestMessage_RequestMessageIsNotNull_SetsRequestMessage()
69+
{
70+
var context = ResilienceContextPool.Shared.Get();
71+
using var request = new HttpRequestMessage();
72+
context.SetRequestMessage(request);
73+
74+
Assert.True(context.Properties.TryGetValue(ResilienceKeys.RequestMessage, out HttpRequestMessage? actualRequest));
75+
Assert.Same(request, actualRequest);
76+
}
77+
}

test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/ResilienceHandlerTest.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.Threading.Tasks;
99
using FluentAssertions;
1010
using Microsoft.Extensions.Http.Diagnostics;
11-
using Microsoft.Extensions.Http.Resilience.Internal;
1211
using Microsoft.Extensions.Http.Resilience.Test.Helpers;
1312
using Polly;
1413
using Xunit;
@@ -108,7 +107,7 @@ public async Task Send_EnsureInvoker(bool executionContextSet, bool asynchronous
108107
handler.InnerHandler = new TestHandlerStub((r, _) =>
109108
{
110109
r.GetResilienceContext().Should().NotBeNull();
111-
r.GetResilienceContext()!.Properties.GetValue(ResilienceKeys.RequestMessage, null!).Should().BeSameAs(r);
110+
r.GetResilienceContext()!.GetRequestMessage().Should().BeSameAs(r);
112111

113112
return Task.FromResult(new HttpResponseMessage { StatusCode = HttpStatusCode.Created });
114113
});

test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Routing/RoutingResilienceStrategyTests.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Net.Http;
66
using FluentAssertions;
7-
using Microsoft.Extensions.Http.Resilience.Internal;
87
using Microsoft.Extensions.Http.Resilience.Routing.Internal;
98
using Moq;
109
using Polly;
@@ -22,14 +21,24 @@ public void NoRequestMessage_Throws()
2221
strategy.Invoking(s => s.Execute(() => { })).Should().Throw<InvalidOperationException>().WithMessage("The HTTP request message was not found in the resilience context.");
2322
}
2423

24+
[Fact]
25+
public void RequestMessageIsNull_Throws()
26+
{
27+
var strategy = Create(() => Mock.Of<RequestRoutingStrategy>());
28+
var context = ResilienceContextPool.Shared.Get();
29+
context.SetRequestMessage(null);
30+
31+
strategy.Invoking(s => s.Execute(_ => { }, context)).Should().Throw<InvalidOperationException>().WithMessage("The HTTP request message was not found in the resilience context.");
32+
}
33+
2534
[Fact]
2635
public void NoRoutingProvider_Ok()
2736
{
2837
using var request = new HttpRequestMessage();
2938

3039
var strategy = Create(null);
3140
var context = ResilienceContextPool.Shared.Get();
32-
context.Properties.Set(ResilienceKeys.RequestMessage, request);
41+
context.SetRequestMessage(request);
3342

3443
strategy.Invoking(s => s.Execute(_ => { }, context)).Should().NotThrow();
3544
}

0 commit comments

Comments
 (0)