-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathResultHttpClientExtensions.cs
More file actions
145 lines (133 loc) · 6.5 KB
/
ResultHttpClientExtensions.cs
File metadata and controls
145 lines (133 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using ManagedCode.Communication;
using ManagedCode.Communication.Extensions;
using Polly;
namespace ManagedCode.Communication.Extensions.Http;
/// <summary>
/// Helpers that execute <see cref="HttpClient"/> requests and transform the responses into
/// <see cref="ManagedCode.Communication.Result"/> instances.
/// </summary>
public static class ResultHttpClientExtensions
{
/// <summary>
/// Sends a request built by <paramref name="requestFactory"/> and converts the HTTP response into a
/// <see cref="Result{T}"/>. When a <paramref name="pipeline"/> is provided the request is executed through it,
/// enabling Polly resilience strategies such as retries or circuit breakers.
/// </summary>
/// <typeparam name="T">The JSON payload type that the endpoint returns in case of success.</typeparam>
/// <param name="client">The <see cref="HttpClient"/> used to send the request.</param>
/// <param name="requestFactory">Factory that creates a fresh <see cref="HttpRequestMessage"/> for each attempt.</param>
/// <param name="pipeline">Optional Polly resilience pipeline that wraps the HTTP invocation.</param>
/// <param name="cancellationToken">Token that cancels the request execution.</param>
public static Task<Result<T>> SendForResultAsync<T>(
this HttpClient client,
Func<HttpRequestMessage> requestFactory,
ResiliencePipeline<HttpResponseMessage>? pipeline = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(requestFactory);
return SendCoreAsync(
client,
requestFactory,
static response => response.FromJsonToResult<T>(),
pipeline,
cancellationToken);
}
/// <summary>
/// Sends a request built by <paramref name="requestFactory"/> and converts the HTTP response into a
/// <see cref="Result"/> without a payload. When a <paramref name="pipeline"/> is provided the request is executed
/// through it, enabling Polly resilience strategies such as retries or circuit breakers.
/// </summary>
/// <param name="client">The <see cref="HttpClient"/> used to send the request.</param>
/// <param name="requestFactory">Factory that creates a fresh <see cref="HttpRequestMessage"/> for each attempt.</param>
/// <param name="pipeline">Optional Polly resilience pipeline that wraps the HTTP invocation.</param>
/// <param name="cancellationToken">Token that cancels the request execution.</param>
public static Task<Result> SendForResultAsync(
this HttpClient client,
Func<HttpRequestMessage> requestFactory,
ResiliencePipeline<HttpResponseMessage>? pipeline = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(requestFactory);
return SendCoreAsync(
client,
requestFactory,
static response => response.FromRequestToResult(),
pipeline,
cancellationToken);
}
/// <summary>
/// Performs a GET request for <paramref name="requestUri"/> and converts the response into a
/// <see cref="Result{T}"/>. The optional <paramref name="pipeline"/> allows attaching Polly retry or circuit
/// breaker strategies.
/// </summary>
/// <typeparam name="T">The JSON payload type that the endpoint returns in case of success.</typeparam>
/// <param name="client">The <see cref="HttpClient"/> used to send the request.</param>
/// <param name="requestUri">The request URI.</param>
/// <param name="pipeline">Optional Polly resilience pipeline that wraps the HTTP invocation.</param>
/// <param name="cancellationToken">Token that cancels the request execution.</param>
public static Task<Result<T>> GetAsResultAsync<T>(
this HttpClient client,
string requestUri,
ResiliencePipeline<HttpResponseMessage>? pipeline = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentException.ThrowIfNullOrEmpty(requestUri);
return client.SendForResultAsync<T>(
() => new HttpRequestMessage(HttpMethod.Get, requestUri),
pipeline,
cancellationToken);
}
/// <summary>
/// Performs a GET request for <paramref name="requestUri"/> and converts the response into a non generic
/// <see cref="Result"/>.
/// </summary>
/// <param name="client">The <see cref="HttpClient"/> used to send the request.</param>
/// <param name="requestUri">The request URI.</param>
/// <param name="pipeline">Optional Polly resilience pipeline that wraps the HTTP invocation.</param>
/// <param name="cancellationToken">Token that cancels the request execution.</param>
public static Task<Result> GetAsResultAsync(
this HttpClient client,
string requestUri,
ResiliencePipeline<HttpResponseMessage>? pipeline = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentException.ThrowIfNullOrEmpty(requestUri);
return client.SendForResultAsync(
() => new HttpRequestMessage(HttpMethod.Get, requestUri),
pipeline,
cancellationToken);
}
private static async Task<TResponse> SendCoreAsync<TResponse>(
HttpClient client,
Func<HttpRequestMessage> requestFactory,
Func<HttpResponseMessage, Task<TResponse>> convert,
ResiliencePipeline<HttpResponseMessage>? pipeline,
CancellationToken cancellationToken)
{
if (pipeline is null)
{
using var request = requestFactory();
using var directResponse = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
return await convert(directResponse).ConfigureAwait(false);
}
var httpResponse = await pipeline.ExecuteAsync(
async cancellationToken =>
{
using var request = requestFactory();
return await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
},
cancellationToken).ConfigureAwait(false);
using (httpResponse)
{
return await convert(httpResponse).ConfigureAwait(false);
}
}
}