Skip to content

Commit af972dd

Browse files
Refactor HttpClientFactoryExtensions to simplify code structure (#2042)
- Extract duplicated extension method implementations into 4 core methods - Use T4 template to auto-generate all 16 overload variants Note: This change is API-compatible but binary-incompatible: - All method parameters now use optional parameter syntax - No code changes required for existing usage - But applications referencing this library need to be recompiled Co-authored-by: Chris Pulman <chris.pulman@yahoo.com>
1 parent f1af4a5 commit af972dd

File tree

5 files changed

+724
-297
lines changed

5 files changed

+724
-297
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+

2+
using System;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Http;
10+
11+
namespace Refit
12+
{
13+
/// <summary>
14+
/// HttpClientFactoryExtensions
15+
/// </summary>
16+
internal static class HttpClientFactoryCore
17+
{
18+
19+
internal static IHttpClientBuilder AddRefitClientCore(
20+
IServiceCollection services,
21+
Type refitInterfaceType,
22+
Func<IServiceProvider, RefitSettings?>? settings,
23+
string? httpClientName
24+
)
25+
{
26+
if (services == null) throw new ArgumentNullException(nameof(services));
27+
if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType));
28+
29+
// register settings
30+
var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType);
31+
services.AddSingleton(
32+
settingsType,
33+
provider => Activator.CreateInstance(
34+
typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!,
35+
settings?.Invoke(provider)
36+
)!
37+
);
38+
39+
// register RequestBuilder
40+
var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType);
41+
services.AddSingleton(
42+
requestBuilderType,
43+
provider => RequestBuilderGenericForTypeMethod
44+
.MakeGenericMethod(refitInterfaceType)
45+
.Invoke(
46+
null,
47+
[((ISettingsFor)provider.GetRequiredService(settingsType)).Settings]
48+
)!
49+
);
50+
51+
// create HttpClientBuilder
52+
var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(refitInterfaceType));
53+
54+
// configure message handler
55+
builder.ConfigureHttpMessageHandlerBuilder(builderConfig =>
56+
{
57+
var handler = CreateInnerHandlerIfProvided(
58+
((ISettingsFor)builderConfig.Services.GetRequiredService(settingsType)).Settings
59+
);
60+
if (handler != null)
61+
{
62+
builderConfig.PrimaryHandler = handler;
63+
}
64+
});
65+
66+
// add typed client (register transient that resolves HttpClient from IHttpClientFactory and creates Refit client)
67+
builder.Services.AddTransient(
68+
refitInterfaceType,
69+
s =>
70+
{
71+
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
72+
var httpClient = httpClientFactory.CreateClient(builder.Name);
73+
return RestService.For(
74+
refitInterfaceType,
75+
httpClient,
76+
(IRequestBuilder)s.GetRequiredService(requestBuilderType)
77+
);
78+
}
79+
);
80+
81+
return builder;
82+
}
83+
84+
internal static IHttpClientBuilder AddKeyedRefitClientCore(
85+
IServiceCollection services,
86+
Type refitInterfaceType,
87+
object? serviceKey,
88+
Func<IServiceProvider, RefitSettings?>? settings,
89+
string? httpClientName
90+
)
91+
{
92+
if (services == null) throw new ArgumentNullException(nameof(services));
93+
if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType));
94+
if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey));
95+
96+
// register settings
97+
var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType);
98+
services.AddKeyedSingleton(
99+
settingsType,
100+
serviceKey,
101+
(provider, _) => Activator.CreateInstance(
102+
typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!,
103+
settings?.Invoke(provider)
104+
)!
105+
);
106+
107+
// register RequestBuilder
108+
var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType);
109+
services.AddKeyedSingleton(
110+
requestBuilderType,
111+
serviceKey,
112+
(provider, _) => RequestBuilderGenericForTypeMethod
113+
.MakeGenericMethod(refitInterfaceType)
114+
.Invoke(
115+
null,
116+
[((ISettingsFor)provider.GetRequiredKeyedService(settingsType, serviceKey)).Settings]
117+
)!
118+
);
119+
120+
// create HttpClientBuilder
121+
var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(refitInterfaceType, serviceKey));
122+
123+
// configure primary handler
124+
builder.ConfigurePrimaryHttpMessageHandler(serviceProvider =>
125+
{
126+
var settingsInstance = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey);
127+
return settingsInstance.Settings?.HttpMessageHandlerFactory?.Invoke() ?? new HttpClientHandler();
128+
});
129+
130+
// configure additional handlers
131+
builder.ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) =>
132+
{
133+
var settingsInstance = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey);
134+
if (settingsInstance.Settings?.AuthorizationHeaderValueGetter is { } getToken)
135+
{
136+
handlers.Add(new AuthenticatedHttpClientHandler(null, getToken));
137+
}
138+
});
139+
140+
// add keyed typed client (register keyed transient that resolves HttpClient and creates Refit client)
141+
builder.Services.AddKeyedTransient(
142+
refitInterfaceType,
143+
serviceKey,
144+
(s, _) =>
145+
{
146+
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
147+
var httpClient = httpClientFactory.CreateClient(builder.Name);
148+
return RestService.For(
149+
refitInterfaceType,
150+
httpClient,
151+
(IRequestBuilder)s.GetRequiredKeyedService(requestBuilderType, serviceKey)
152+
);
153+
}
154+
);
155+
156+
return builder;
157+
}
158+
159+
internal static IHttpClientBuilder AddRefitClientCore<T>(
160+
IServiceCollection services,
161+
Type refitInterfaceType,
162+
Func<IServiceProvider, RefitSettings?>? settings,
163+
string? httpClientName
164+
) where T : class
165+
{
166+
if (services == null) throw new ArgumentNullException(nameof(services));
167+
168+
// register settings
169+
services.AddSingleton(provider => new SettingsFor<T>(settings?.Invoke(provider)));
170+
171+
// register RequestBuilder
172+
services.AddSingleton(provider =>
173+
RequestBuilder.ForType<T>(provider.GetRequiredService<SettingsFor<T>>().Settings)
174+
);
175+
176+
// create HttpClientBuilder
177+
var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType<T>());
178+
179+
// configure message handler
180+
builder.ConfigureHttpMessageHandlerBuilder(builderConfig =>
181+
{
182+
var handler = CreateInnerHandlerIfProvided(
183+
builderConfig.Services.GetRequiredService<SettingsFor<T>>().Settings
184+
);
185+
if (handler != null)
186+
{
187+
builderConfig.PrimaryHandler = handler;
188+
}
189+
});
190+
191+
// add typed client using framework AddTypedClient
192+
return builder.AddTypedClient((client, serviceProvider) =>
193+
RestService.For<T>(
194+
client,
195+
serviceProvider.GetRequiredService<IRequestBuilder<T>>()
196+
)
197+
);
198+
}
199+
200+
internal static IHttpClientBuilder AddKeyedRefitClientCore<T>(
201+
IServiceCollection services,
202+
Type refitInterfaceType,
203+
object? serviceKey,
204+
Func<IServiceProvider, RefitSettings?>? settings,
205+
string? httpClientName
206+
) where T : class
207+
{
208+
if (services == null) throw new ArgumentNullException(nameof(services));
209+
if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey));
210+
211+
// register settings
212+
services.AddKeyedSingleton(
213+
serviceKey,
214+
(provider, _) => new SettingsFor<T>(settings?.Invoke(provider))
215+
);
216+
217+
// register RequestBuilder
218+
services.AddKeyedSingleton(
219+
serviceKey,
220+
(provider, _) =>
221+
RequestBuilder.ForType<T>(
222+
provider.GetRequiredKeyedService<SettingsFor<T>>(serviceKey).Settings
223+
)
224+
);
225+
226+
// create HttpClientBuilder
227+
var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType<T>(serviceKey));
228+
229+
// configure primary handler
230+
builder.ConfigurePrimaryHttpMessageHandler(serviceProvider =>
231+
{
232+
var settingsInstance = serviceProvider.GetRequiredKeyedService<SettingsFor<T>>(serviceKey).Settings;
233+
return settingsInstance?.HttpMessageHandlerFactory?.Invoke() ?? new HttpClientHandler();
234+
});
235+
236+
// configure additional handlers
237+
builder.ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) =>
238+
{
239+
var settingsInstance = serviceProvider.GetRequiredKeyedService<SettingsFor<T>>(serviceKey).Settings;
240+
if (settingsInstance?.AuthorizationHeaderValueGetter is { } getToken)
241+
{
242+
handlers.Add(new AuthenticatedHttpClientHandler(null, getToken));
243+
}
244+
});
245+
246+
// add keyed typed client (inline keyed registration)
247+
builder.Services.AddKeyedTransient(
248+
serviceKey,
249+
(s, _) =>
250+
{
251+
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
252+
var httpClient = httpClientFactory.CreateClient(builder.Name);
253+
return RestService.For<T>(
254+
httpClient,
255+
s.GetRequiredKeyedService<IRequestBuilder<T>>(serviceKey)
256+
);
257+
}
258+
);
259+
260+
return builder;
261+
}
262+
263+
// helper - used by AddRefitClientCore and AddRefitClientCore<T>
264+
private static HttpMessageHandler? CreateInnerHandlerIfProvided(RefitSettings? settings)
265+
{
266+
HttpMessageHandler? innerHandler = null;
267+
if (settings != null)
268+
{
269+
if (settings.HttpMessageHandlerFactory != null)
270+
{
271+
innerHandler = settings.HttpMessageHandlerFactory();
272+
}
273+
274+
if (settings.AuthorizationHeaderValueGetter != null)
275+
{
276+
innerHandler = new AuthenticatedHttpClientHandler(
277+
settings.AuthorizationHeaderValueGetter,
278+
innerHandler
279+
);
280+
}
281+
}
282+
283+
return innerHandler;
284+
}
285+
286+
private static readonly MethodInfo RequestBuilderGenericForTypeMethod =
287+
typeof(RequestBuilder)
288+
.GetMethods(BindingFlags.Public | BindingFlags.Static)
289+
.Single(z => z.IsGenericMethodDefinition && z.GetParameters().Length == 1);
290+
}
291+
}

0 commit comments

Comments
 (0)