Skip to content

Commit 66e5829

Browse files
committed
Add HttpClientBuilderExtensions.AddDibixHttpClient
1 parent 8dfe1e4 commit 66e5829

6 files changed

Lines changed: 193 additions & 28 deletions
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Net.Http;
6+
using System.Reflection;
7+
using Dibix.Http.Client;
8+
using Microsoft.Extensions.DependencyInjection.Extensions;
9+
using Microsoft.Extensions.Options;
10+
11+
namespace Microsoft.Extensions.DependencyInjection
12+
{
13+
public static class HttpClientBuilderExtensions
14+
{
15+
public static IHttpClientBuilder AddBuiltinHttpMessageHandlers(this IHttpClientBuilder builder, params Type[] excludedHandlers)
16+
{
17+
AddHttpMessageHandler<TraceProxyHttpMessageHandler>(builder, excludedHandlers);
18+
AddHttpMessageHandler<EnsureSuccessStatusCodeHttpMessageHandler>(builder, excludedHandlers);
19+
AddHttpMessageHandler<TraceSourceHttpMessageHandler>(builder, excludedHandlers);
20+
return builder;
21+
}
22+
23+
public static void AddDibixHttpClient(this IHttpClientBuilder httpClientBuilder, Action<IHttpServiceDiscoveryConfiguration> configure)
24+
{
25+
HttpServiceConfiguration configuration = new HttpServiceConfiguration(httpClientBuilder);
26+
configure(configuration);
27+
28+
IServiceCollection services = httpClientBuilder.Services;
29+
services.Configure(configuration.ClientConfiguration ?? (_ => { }));
30+
31+
RegisterHttpClients(services, configuration.AssemblyCollector, httpClientBuilder.Name);
32+
}
33+
34+
private static void AddHttpMessageHandler<T>(IHttpClientBuilder builder, IEnumerable<Type> excludedHandlers) where T : DelegatingHandler, new()
35+
{
36+
if (excludedHandlers.Contains(typeof(T)))
37+
return;
38+
39+
builder.AddHttpMessageHandler(() => new T());
40+
}
41+
42+
private static void RegisterHttpClients(IServiceCollection services, Func<ICollection<Assembly>> assemblyCollector, string httpClientName)
43+
{
44+
foreach (Assembly assembly in assemblyCollector())
45+
{
46+
if (!assembly.IsDefined(typeof(ArtifactAssemblyAttribute)))
47+
continue;
48+
49+
foreach (Type type in assembly.GetTypes())
50+
{
51+
if (!type.IsClass)
52+
continue;
53+
54+
HttpServiceAttribute httpServiceAttribute = type.GetCustomAttribute<HttpServiceAttribute>();
55+
if (httpServiceAttribute == null)
56+
continue;
57+
58+
services.AddScoped(httpServiceAttribute.ContractType, CompileImplementationFactory(type, httpClientName));
59+
}
60+
}
61+
}
62+
63+
private static Func<IServiceProvider, object> CompileImplementationFactory(Type implementationType, string httpClientName)
64+
{
65+
MethodCallExpression ResolveService(Type contractType, Expression serviceProvider) => Expression.Call(typeof(ServiceProviderServiceExtensions), nameof(ServiceProviderServiceExtensions.GetRequiredService), [contractType], serviceProvider);
66+
67+
Expression CollectParameter(string parameterName, Type parameterType, Expression serviceProvider) => parameterName switch
68+
{
69+
"httpClientOptions" when parameterType == typeof(HttpClientOptions) => Expression.Property(ResolveService(typeof(IOptions<HttpClientOptions>), serviceProvider), nameof(IOptions<object>.Value)),
70+
"httpClientName" when parameterType == typeof(string) => Expression.Constant(httpClientName),
71+
_ => ResolveService(parameterType, serviceProvider)
72+
};
73+
74+
ParameterExpression serviceProviderParameter = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
75+
76+
ConstructorInfo constructor = HttpServiceConstructorSelector.SelectConstructor(implementationType);
77+
IEnumerable<Expression> parameters = constructor.GetParameters().Select(x => CollectParameter(x.Name!, x.ParameterType, serviceProviderParameter));
78+
Expression value = Expression.New(constructor, parameters);
79+
80+
Expression<Func<IServiceProvider, object>> lambda = Expression.Lambda<Func<IServiceProvider, object>>(value, serviceProviderParameter);
81+
Func<IServiceProvider, object> compiled = lambda.Compile();
82+
return compiled;
83+
}
84+
85+
private sealed class HttpServiceConfiguration : IHttpServiceDiscoveryConfiguration, IHttpServiceInfrastructureConfiguration
86+
{
87+
private readonly IHttpClientBuilder _httpClientBuilder;
88+
89+
public Func<ICollection<Assembly>> AssemblyCollector { get; private set; } = () => [];
90+
public Action<HttpClientOptions> ClientConfiguration { get; set; }
91+
92+
public HttpServiceConfiguration(IHttpClientBuilder httpClientBuilder)
93+
{
94+
_httpClientBuilder = httpClientBuilder;
95+
}
96+
97+
IHttpServiceInfrastructureConfiguration IHttpServiceDiscoveryConfiguration.FromAssemblies(IEnumerable<Assembly> assemblies)
98+
{
99+
AssemblyCollector = () => HttpServiceAssemblyDiscovery.FromAssemblies(assemblies);
100+
return this;
101+
}
102+
103+
IHttpServiceInfrastructureConfiguration IHttpServiceDiscoveryConfiguration.FromAssembly(Assembly assembly)
104+
{
105+
AssemblyCollector = () => HttpServiceAssemblyDiscovery.FromAssembly(assembly);
106+
return this;
107+
}
108+
109+
IHttpServiceInfrastructureConfiguration IHttpServiceDiscoveryConfiguration.FromAssemblyContaining(Type type)
110+
{
111+
AssemblyCollector = () => HttpServiceAssemblyDiscovery.FromAssemblyContaining(type);
112+
return this;
113+
}
114+
115+
IHttpServiceInfrastructureConfiguration IHttpServiceInfrastructureConfiguration.WithAuthorizationProvider<TAuthorizationProvider>()
116+
{
117+
_httpClientBuilder.Services.TryAddScoped<IHttpAuthorizationProvider, TAuthorizationProvider>();
118+
return this;
119+
}
120+
121+
void IHttpServiceInfrastructureConfiguration.Configure(Action<HttpClientOptions> configure)
122+
{
123+
ClientConfiguration = configure;
124+
}
125+
}
126+
127+
private static class HttpServiceAssemblyDiscovery
128+
{
129+
public static ICollection<Assembly> FromAssemblies(IEnumerable<Assembly> assemblies) => assemblies.ToArray();
130+
131+
public static ICollection<Assembly> FromAssembly(Assembly assembly) => [assembly];
132+
133+
public static ICollection<Assembly> FromAssemblyContaining(Type type) => FromAssembly(type.Assembly);
134+
}
135+
}
136+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net.Http;
4+
using System.Reflection;
5+
6+
namespace Dibix.Http.Client
7+
{
8+
internal static class HttpServiceConstructorSelector
9+
{
10+
private static readonly Type[][] ConstructorSignatures =
11+
[
12+
[typeof(IHttpClientFactory), typeof(HttpClientOptions), typeof(IHttpAuthorizationProvider), typeof(string)]
13+
, [typeof(IHttpClientFactory), typeof(HttpClientOptions), typeof(string)]
14+
];
15+
16+
public static ConstructorInfo SelectConstructor(Type implementationType)
17+
{
18+
foreach (ConstructorInfo constructor in implementationType.GetConstructors())
19+
{
20+
foreach (Type[] constructorSignature in ConstructorSignatures)
21+
{
22+
if (constructorSignature.SequenceEqual(constructor.GetParameters().Select(x => x.ParameterType)))
23+
return constructor;
24+
}
25+
}
26+
27+
string constructorSignatures = String.Join(Environment.NewLine, ConstructorSignatures.Select(x => $"- ({String.Join(", ", x.Select(y => y.ToString()))})"));
28+
throw new InvalidOperationException($@"Could not find a matching constructor candidate on type '{implementationType}'. Tried:
29+
{constructorSignatures}");
30+
}
31+
}
32+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
namespace Dibix.Http.Client
6+
{
7+
public interface IHttpServiceDiscoveryConfiguration
8+
{
9+
IHttpServiceInfrastructureConfiguration FromAssemblies(IEnumerable<Assembly> assemblies);
10+
IHttpServiceInfrastructureConfiguration FromAssembly(Assembly assembly);
11+
IHttpServiceInfrastructureConfiguration FromAssemblyContaining(Type type);
12+
}
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace Dibix.Http.Client
4+
{
5+
public interface IHttpServiceInfrastructureConfiguration
6+
{
7+
IHttpServiceInfrastructureConfiguration WithAuthorizationProvider<TAuthorizationProvider>() where TAuthorizationProvider : class, IHttpAuthorizationProvider;
8+
void Configure(Action<HttpClientOptions> configure);
9+
}
10+
}

src/Dibix.Http.Client/Client/HttpClientBuilderExtensions.cs

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2-
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp80</s:String>
2+
33
<s:String x:Key="/Default/CodeInspection/Daemon/ConfigureAwaitAnalysisMode/@EntryValue">Library</s:String>
4+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=aspnetcore/@EntryIndexedValue">True</s:Boolean>
45
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client/@EntryIndexedValue">True</s:Boolean>
56
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Chandlers/@EntryIndexedValue">True</s:Boolean>
67
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=description/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

0 commit comments

Comments
 (0)