Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Scrutor.Extensions.HttpClient/Extensions/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Linq;
using System.Reflection;
#if NET8_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using Scrutor;

namespace Microsoft.Extensions.DependencyInjection;
Expand All @@ -14,12 +17,24 @@ namespace Microsoft.Extensions.DependencyInjection;
/// See https://github.com/khellang/Scrutor/issues/180.</remarks>
public static class HttpClientExtensions
{
// Honest trim/AOT signal: registration runs Scrutor's runtime assembly scan and closes the
// generic AddHttpClient<TClient,TImplementation> over the scanned types via reflection. Consumers
// building trimmed/AOT get a warning at these entry points instead of silent breakage.
private const string TrimMessage =
"Registers types discovered by Scrutor's runtime assembly scan as typed HttpClients; the scanned client types may be removed by the trimmer.";
private const string AotMessage =
"Closes the generic AddHttpClient<TClient,TImplementation> over the scanned types via MakeGenericMethod, which requires runtime code generation.";

/// <summary>
/// Registers each scanned service as a typed <see cref="System.Net.Http.HttpClient"/> bound to
/// the named client <paramref name="name"/> (so they share that client's configuration).
/// </summary>
/// <param name="selector">The Scrutor service-type selector.</param>
/// <param name="name">The name of a client registered via <c>AddHttpClient(name, ...)</c>.</param>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(TrimMessage)]
[RequiresDynamicCode(AotMessage)]
#endif
public static IServiceTypeSelector AsHttpClient(this IServiceTypeSelector selector, string name = "")
=> selector.UsingRegistrationStrategy(new HttpClientRegistrationStrategy(name));

Expand All @@ -28,6 +43,10 @@ public static IServiceTypeSelector AsHttpClient(this IServiceTypeSelector select
/// its own default client named after the service type.
/// </summary>
/// <param name="selector">The Scrutor service-type selector.</param>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(TrimMessage)]
[RequiresDynamicCode(AotMessage)]
#endif
public static IServiceTypeSelector AsHttpClient(this IServiceTypeSelector selector)
=> selector.UsingRegistrationStrategy(new HttpClientRegistrationStrategy(name: null));

Expand All @@ -44,6 +63,13 @@ private sealed class HttpClientRegistrationStrategy(string? name) : Registration
private static readonly MethodInfo NamedAddHttpClient = ResolveAddHttpClient(named: true);
private static readonly MethodInfo UnnamedAddHttpClient = ResolveAddHttpClient(named: false);

// Apply overrides Scrutor's (un-annotated) RegistrationStrategy.Apply, so the RequiresDynamicCode
// warning can't propagate here. It's already surfaced to consumers on the public AsHttpClient
// entry points (the only way to reach this strategy), so suppress the internal MakeGenericMethod site.
#if NET8_0_OR_GREATER
[UnconditionalSuppressMessage("AOT", "IL3050",
Justification = "AsHttpClient() entry points are marked [RequiresDynamicCode]; callers are warned. AddHttpClient<,> has no non-generic overload.")]
#endif
public override void Apply(IServiceCollection services, ServiceDescriptor descriptor)
{
var serviceType = descriptor.ServiceType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>

<!-- Turn on the trim + AOT analyzers on the modern target so our own build validates the
[RequiresUnreferencedCode]/[RequiresDynamicCode] annotations are well-formed and the
intentional reflection is properly justified. (netstandard2.0 has no such analyzers.) -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
</PropertyGroup>

<PropertyGroup Label="Package">
<Title>Scrutor HttpClient Extension</Title>
<Authors>Chrison Simtian</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static class HttpClientExtensions
{
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Closes the generic AddHttpClient<TClient,TImplementation> over the scanned types " +
"via MakeGenericMethod, which requires runtime code generation.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Registers types discovered by Scrutor\'s runtime assembly scan as typed HttpClient" +
"s; the scanned client types may be removed by the trimmer.")]
public static Scrutor.IServiceTypeSelector AsHttpClient(this Scrutor.IServiceTypeSelector selector) { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Closes the generic AddHttpClient<TClient,TImplementation> over the scanned types " +
"via MakeGenericMethod, which requires runtime code generation.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Registers types discovered by Scrutor\'s runtime assembly scan as typed HttpClient" +
"s; the scanned client types may be removed by the trimmer.")]
public static Scrutor.IServiceTypeSelector AsHttpClient(this Scrutor.IServiceTypeSelector selector, string name = "") { }
}
}
Loading