-
Notifications
You must be signed in to change notification settings - Fork 1
Add back typed part #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add back typed part #158
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,295 @@ | ||
| using System; | ||
| using System.Linq; | ||
| using System.Net; | ||
| using System.Net.Http; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Activout.RestClient.Helpers; | ||
| using Activout.RestClient.Helpers.Implementation; | ||
| using Activout.RestClient.Newtonsoft.Json.Test.MovieReviews; | ||
| using Activout.RestClient.ParamConverter; | ||
| using Activout.RestClient.ParamConverter.Implementation; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.DependencyInjection.Extensions; | ||
| using Microsoft.Extensions.Http; | ||
| using Microsoft.Extensions.Logging; | ||
| using Newtonsoft.Json; | ||
| using RichardSzalay.MockHttp; | ||
| using Xunit; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace Activout.RestClient.Newtonsoft.Json.Test; | ||
|
|
||
| public class HttpClientFactoryWithDelegatingHandlerTest | ||
| { | ||
| private const string BaseUri = "https://example.com/movieReviewService"; | ||
| private const string MovieId = "test-movie-123"; | ||
|
|
||
| private readonly ITestOutputHelper _outputHelper; | ||
| private readonly MockHttpMessageHandler _mockHttp = new(); | ||
|
|
||
| public HttpClientFactoryWithDelegatingHandlerTest(ITestOutputHelper outputHelper) | ||
| { | ||
| _outputHelper = outputHelper; | ||
| } | ||
|
|
||
| private class LoggingDelegatingHandler : DelegatingHandler | ||
| { | ||
| private readonly ITestOutputHelper _outputHelper; | ||
|
|
||
| public LoggingDelegatingHandler(ITestOutputHelper outputHelper) | ||
| { | ||
| _outputHelper = outputHelper; | ||
| } | ||
|
|
||
| protected override async Task<HttpResponseMessage> SendAsync( | ||
| HttpRequestMessage request, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| _outputHelper.WriteLine($"[Request] {request.Method} {request.RequestUri}"); | ||
|
|
||
| if (request.Content != null) | ||
| { | ||
| // Buffer the content so we can read it multiple times | ||
| await request.Content.LoadIntoBufferAsync(); | ||
| var requestContent = await request.Content.ReadAsStringAsync(cancellationToken); | ||
| _outputHelper.WriteLine($"[Request Content] {requestContent}"); | ||
| } | ||
|
|
||
| var response = await base.SendAsync(request, cancellationToken); | ||
|
|
||
| _outputHelper.WriteLine($"[Response] {response.StatusCode}"); | ||
|
|
||
| await response.Content.LoadIntoBufferAsync(); | ||
| var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); | ||
| _outputHelper.WriteLine($"[Response Content] {responseContent}"); | ||
|
|
||
| return response; | ||
| } | ||
| } | ||
|
|
||
| private class MovieReviewServiceFactory | ||
| { | ||
| private readonly HttpClient _httpClient; | ||
| private readonly IRestClientFactory _restClientFactory; | ||
| private readonly ILogger<MovieReviewServiceFactory> _logger; | ||
|
|
||
| public MovieReviewServiceFactory( | ||
| HttpClient httpClient, | ||
| IRestClientFactory restClientFactory, | ||
| ILogger<MovieReviewServiceFactory> logger) | ||
| { | ||
| _httpClient = httpClient; | ||
| _restClientFactory = restClientFactory; | ||
| _logger = logger; | ||
| } | ||
|
|
||
| public IMovieReviewService CreateMovieReviewService() | ||
| { | ||
| return _restClientFactory.CreateBuilder() | ||
| .WithNewtonsoftJson() | ||
| .With(_logger) | ||
| .With(_httpClient) | ||
| .BaseUri(BaseUri) | ||
| .Build<IMovieReviewService>(); | ||
| } | ||
| } | ||
|
|
||
| private static IServiceCollection AddRestClient(IServiceCollection services) | ||
| { | ||
| services.TryAddTransient<IDuckTyping, DuckTyping>(); | ||
| services.TryAddTransient<IParamConverterManager, ParamConverterManager>(); | ||
| services.TryAddTransient<IRestClientFactory, RestClientFactory>(); | ||
| services.TryAddTransient<ITaskConverterFactory, TaskConverter3Factory>(); | ||
| return services; | ||
| } | ||
|
|
||
| private IServiceProvider CreateServiceProvider() | ||
| { | ||
| var services = new ServiceCollection(); | ||
| services.AddTransient<HttpMessageHandlerBuilder>(_ => new MockHttpMessageHandlerBuilder(_mockHttp)); | ||
|
|
||
| AddRestClient(services); | ||
|
|
||
| services.AddLogging(builder => | ||
| { | ||
| builder | ||
| .AddFilter("Microsoft", LogLevel.Warning) | ||
| .AddFilter("System", LogLevel.Warning) | ||
| .AddFilter("Activout.RestClient", LogLevel.Debug) | ||
| .AddXUnit(_outputHelper); | ||
| }); | ||
|
|
||
| services.AddHttpClient<MovieReviewServiceFactory>() | ||
| .AddHttpMessageHandler(() => new LoggingDelegatingHandler(_outputHelper)); | ||
|
|
||
| return services.BuildServiceProvider(); | ||
| } | ||
|
|
||
| private IMovieReviewService CreateMovieReviewService() | ||
| { | ||
| var serviceProvider = CreateServiceProvider(); | ||
| var factory = serviceProvider.GetRequiredService<MovieReviewServiceFactory>(); | ||
| return factory.CreateMovieReviewService(); | ||
| } | ||
|
Comment on lines
+108
to
+135
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
rg -n -C3 'BuildServiceProvider\(|CreateServiceProvider\(|IDisposable|Dispose\(' \
Activout.RestClient.Newtonsoft.Json.Test/HttpClientFactoryWithDelegatingHandlerTest.csRepository: twogood/Activout.RestClient Length of output: 1350 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
# Inspect the test class structure and the relevant helper implementations.
sed -n '1,180p' Activout.RestClient.Newtonsoft.Json.Test/HttpClientFactoryWithDelegatingHandlerTest.rsRepository: twogood/Activout.RestClient Length of output: 295 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
sed -n '1,220p' 'Activout.RestClient.Newtonsoft.Json.Test/HttpClientFactoryWithDelegatingHandlerTest.cs'Repository: twogood/Activout.RestClient Length of output: 7762 Dispose the root service providers.
Also applies to: 137-159 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| private IMovieReviewService CreateMovieReviewServiceWithConfiguredPrimaryHandler() | ||
| { | ||
| var services = new ServiceCollection(); | ||
|
|
||
| AddRestClient(services); | ||
|
|
||
| services.AddLogging(builder => | ||
| { | ||
| builder | ||
| .AddFilter("Microsoft", LogLevel.Warning) | ||
| .AddFilter("System", LogLevel.Warning) | ||
| .AddFilter("Activout.RestClient", LogLevel.Debug) | ||
| .AddXUnit(_outputHelper); | ||
| }); | ||
|
|
||
| services.AddHttpClient<MovieReviewServiceFactory>() | ||
| .ConfigurePrimaryHttpMessageHandler(() => _mockHttp) | ||
| .AddHttpMessageHandler(() => new LoggingDelegatingHandler(_outputHelper)); | ||
|
|
||
| var serviceProvider = services.BuildServiceProvider(); | ||
| var factory = serviceProvider.GetRequiredService<MovieReviewServiceFactory>(); | ||
| return factory.CreateMovieReviewService(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task TestGetWithDelegatingHandler_ShouldDeserializeSuccessfully() | ||
| { | ||
| // Arrange | ||
| var movies = new[] | ||
| { | ||
| new Movie { Title = "Test Movie 1" }, | ||
| new Movie { Title = "Test Movie 2" } | ||
| }; | ||
|
|
||
| _mockHttp | ||
| .Expect(HttpMethod.Get, $"{BaseUri}/movies") | ||
| .Respond("application/json", JsonConvert.SerializeObject(movies)); | ||
|
|
||
| var reviewSvc = CreateMovieReviewServiceWithConfiguredPrimaryHandler(); | ||
|
|
||
| // Act | ||
| var result = await reviewSvc.GetAllMovies(); | ||
|
|
||
| // Assert | ||
| _mockHttp.VerifyNoOutstandingExpectation(); | ||
| Assert.NotNull(result); | ||
| Assert.Equal(2, result.Count()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task TestGetReviewsWithDelegatingHandler_ShouldDeserializeSuccessfully() | ||
| { | ||
| // Arrange | ||
| var reviews = new[] | ||
| { | ||
| new Review(5, "Great movie!") { MovieId = MovieId, ReviewId = "rev1" }, | ||
| new Review(4, "Good movie") { MovieId = MovieId, ReviewId = "rev2" } | ||
| }; | ||
|
|
||
| _mockHttp | ||
| .Expect(HttpMethod.Get, $"{BaseUri}/movies/{MovieId}/reviews") | ||
| .Respond("application/json", JsonConvert.SerializeObject(reviews)); | ||
|
|
||
| var reviewSvc = CreateMovieReviewService(); | ||
|
|
||
| var result = await reviewSvc.GetAllReviews(MovieId); | ||
|
|
||
| // Assert | ||
| _mockHttp.VerifyNoOutstandingExpectation(); | ||
| Assert.NotNull(result); | ||
| Assert.Equal(2, result.Count()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task TestUnsafeWhenWithReusedHttpContent_SecondCallThrowsObjectDisposedException() | ||
| { | ||
| // Arrange | ||
| var movies = new[] | ||
| { | ||
| new Movie { Title = "Test Movie 1" }, | ||
| new Movie { Title = "Test Movie 2" } | ||
| }; | ||
| var sharedContent = new StringContent( | ||
| JsonConvert.SerializeObject(movies), | ||
| Encoding.UTF8, | ||
| "application/json"); | ||
|
|
||
| // Intentionally unsafe: same HttpContent instance is returned for every request. | ||
| _mockHttp | ||
| .When(HttpMethod.Get, $"{BaseUri}/movies") | ||
| .Respond(_ => sharedContent); | ||
|
|
||
| var reviewSvc = CreateMovieReviewService(); | ||
|
|
||
| // Act | ||
| var firstCallResult = await reviewSvc.GetAllMovies(); | ||
|
|
||
| // Assert first call succeeds, second call reuses disposed content and fails. | ||
| Assert.NotNull(firstCallResult); | ||
| Assert.Equal(2, firstCallResult.Count()); | ||
|
|
||
| await Assert.ThrowsAsync<ObjectDisposedException>(() => reviewSvc.GetAllMovies()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task TestPostWithDelegatingHandler_ShouldSubmitReview() | ||
| { | ||
| // Arrange | ||
| var review = new Review(5, "Amazing!") { MovieId = MovieId, ReviewId = "new-review" }; | ||
|
|
||
| _mockHttp | ||
| .Expect(HttpMethod.Post, $"{BaseUri}/movies/{MovieId}/reviews") | ||
| .Respond("application/json", JsonConvert.SerializeObject(review)); | ||
|
|
||
| var reviewSvc = CreateMovieReviewService(); | ||
|
|
||
| // Act | ||
| var result = await reviewSvc.SubmitReview(MovieId, review); | ||
|
|
||
| // Assert | ||
| _mockHttp.VerifyNoOutstandingExpectation(); | ||
| Assert.NotNull(result); | ||
| Assert.Equal(review.ReviewId, result.ReviewId); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task TestErrorResponseWithDelegatingHandler_ShouldDeserializeErrorResponse() | ||
| { | ||
| // Arrange | ||
| _mockHttp | ||
| .Expect(HttpMethod.Get, $"{BaseUri}/movies/{MovieId}/reviews") | ||
| .Respond(HttpStatusCode.NotFound, _ => new StringContent( | ||
| JsonConvert.SerializeObject(new | ||
| { | ||
| Errors = new object[] | ||
| { | ||
| new { Message = "Movie not found", Code = 404 } | ||
| } | ||
| }), | ||
| Encoding.UTF8, | ||
| "application/json")); | ||
|
|
||
| var reviewSvc = CreateMovieReviewService(); | ||
|
|
||
| // Act & Assert | ||
| // The error response also goes through the delegating handler | ||
| // and needs to be deserialized, so this is another path where | ||
| // ObjectDisposedException could occur | ||
| var exception = await Assert.ThrowsAsync<RestClientException>(() => reviewSvc.GetAllReviews(MovieId)); | ||
|
|
||
| _mockHttp.VerifyNoOutstandingExpectation(); | ||
| Assert.Equal(HttpStatusCode.NotFound, exception.StatusCode); | ||
|
|
||
| var error = exception.GetErrorResponse<ErrorResponse>(); | ||
| Assert.NotNull(error); | ||
| Assert.Single(error.Errors); | ||
| Assert.Equal(404, error.Errors[0].Code); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Net.Http; | ||
| using Microsoft.Extensions.Http; | ||
| using RichardSzalay.MockHttp; | ||
|
|
||
| namespace Activout.RestClient.Newtonsoft.Json.Test; | ||
|
|
||
| internal sealed class MockHttpMessageHandlerBuilder : HttpMessageHandlerBuilder | ||
| { | ||
| private readonly MockHttpMessageHandler _mockHttp; | ||
|
|
||
| public MockHttpMessageHandlerBuilder(MockHttpMessageHandler mockHttp) | ||
| { | ||
| _mockHttp = mockHttp; | ||
| } | ||
|
|
||
| [DisallowNull] | ||
| public override string? Name { get; set; } | ||
|
|
||
| public override HttpMessageHandler PrimaryHandler | ||
| { | ||
| get => _mockHttp; | ||
| set { } | ||
| } | ||
|
Comment on lines
+21
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: No, IHttpClientBuilder.ConfigurePrimaryHttpMessageHandler does not inherently assign the value to HttpMessageHandlerBuilder.PrimaryHandler while constructing the handler pipeline. Instead, ConfigurePrimaryHttpMessageHandler works by adding configuration actions to the HttpClientFactoryOptions [1]. When a client is created, the HttpClientFactory infrastructure runs these actions to configure the HttpMessageHandlerBuilder instance [2][1]. Specifically, there are different overloads of ConfigurePrimaryHttpMessageHandler that behave differently: 1. Overloads that accept a delegate to create/replace the handler (e.g., Func<IServiceProvider, HttpMessageHandler>) directly modify or replace the primary handler [3][1]. 2. The overload that accepts an Action<HttpMessageHandler, IServiceProvider> allows you to configure the existing primary handler [3][1]. In this specific case, the action is added to the builder's configuration actions, and when it executes, it receives the current PrimaryHandler instance as an argument, allowing you to modify it [1]. The assignment to the PrimaryHandler property on the builder typically happens as part of the internal pipeline construction process where the builder's state is finalized before the pipeline is built via CreateHandlerPipeline [4][5]. Therefore, the extension method adds the logic to perform the configuration, but it does not perform a direct assignment at the moment the extension method is called [1]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Files matching MockHttpMessageHandlerBuilder.cs:"
fd -a 'MockHttpMessageHandlerBuilder\.cs$' . || true
file="$(fd 'MockHttpMessageHandlerBuilder\.cs$' . | head -n 1 || true)"
if [ -n "${file:-}" ]; then
echo
echo "Line count:"
wc -l "$file"
echo
echo "Outline:"
ast-grep outline "$file" || true
echo
echo "Contents:"
cat -n "$file"
fi
echo
echo "Search for MockHttpMessageHandlerBuilder registrations/usages:"
rg -n "MockHttpMessageHandlerBuilder|PrimaryHandler|ConfigurePrimaryHttpMessageHandler|AddTestClient|AddClient" . -g '*.cs' || trueRepository: twogood/Activout.RestClient Length of output: 3219 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Relevant test locations:"
cat -n Activout.RestClient.Newtonsoft.Json.Test/HttpClientFactoryWithDelegatingHandlerTest.cs | sed -n '80,185p'
echo
echo "Find HttpMessageHandlerBuilder/HttpClientFactoryOptions/Builder references in library/test:"
rg -n "HttpMessageHandlerBuilder|HttpClientFactoryOptions|BuildPrimaryMessageHandler|CreateHandlerPipeline|ConfigurePrimaryHttpMessageHandler|AddTestClient" . -g '*.cs' -g '*.csproj' || trueRepository: twogood/Activout.RestClient Length of output: 5487 🌐 Web query:
💡 Result: In the context of.NET and OpenTelemetry instrumentation, the terms you mentioned relate to how HTTP requests are handled, traced, and configured using the Microsoft Extensions HTTP infrastructure. HttpMessageHandlerBuilder.PrimaryHandler The PrimaryHandler is a property of the HttpMessageHandlerBuilder class [1][2]. It represents the base HttpMessageHandler responsible for actually sending the HTTP request over the network [3]. In an HttpClient pipeline, it sits at the bottom of the handler chain [3]. When using IHttpClientFactory, you can configure this handler using the ConfigurePrimaryHttpMessageHandler extension method [4][5]. OpenTelemetry Instrumentation and HttpMessageListener OpenTelemetry does not typically require you to manually interact with a "PrimaryHandler" setter for instrumentation purposes [6][7]. Instead, OpenTelemetry's.NET instrumentation for HttpClient (found in the OpenTelemetry.Instrumentation.Http library) works primarily by: 1. Subscribing to DiagnosticSource events (like System.Net.Http.HttpRequestOut.Start) [8]. 2. Using HttpClientTraceInstrumentationOptions to allow users to filter or enrich telemetry (e.g., via EnrichWithHttpRequestMessage) [9][7]. Relationship There is no direct "OpenTelemetry Instrumentation HttpMessageListener PrimaryHandler setter." If you need to add custom logic to your HTTP requests (such as logging, custom headers, or specific transport settings) while maintaining OpenTelemetry instrumentation, you should: - Use ConfigurePrimaryHttpMessageHandler to set your custom SocketsHttpHandler or HttpClientHandler [4]. - Use AddHttpMessageHandler to add custom DelegatingHandler instances to the pipeline [10]. OpenTelemetry will continue to collect telemetry because it instruments the HttpClient/HttpMessageHandler infrastructure at a lower level via DiagnosticSource [8][6]. When configuring the PrimaryHandler, note that in modern.NET versions, the default primary handler is SocketsHttpHandler [5]. If you override it, ensure your code remains compatible with the required handler types to avoid runtime errors (e.g., InvalidCastException if you expect an HttpClientHandler but receive a SocketsHttpHandler) [5]. Citations:
🌐 Web query:
💡 Result: In the context of ASP.NET Core and IHttpClientFactory, these components are part of the infrastructure used to configure the primary message handler of an HttpClient [1][2]. HttpMessageHandlerBuilder is a class responsible for constructing the HttpMessageHandler pipeline [3]. It contains a PrimaryHandler property, which is an abstract HttpMessageHandler that can be get or set [4]. When you use the IHttpClientBuilder extensions, the infrastructure internally manipulates this builder [5][6]. ConfigurePrimaryHttpMessageHandler is an extension method available on IHttpClientBuilder [7][8]. It allows you to provide a delegate that defines how the primary HttpMessageHandler should be created or configured [1][2]. Under the hood, when you call ConfigurePrimaryHttpMessageHandler, the framework registers an action with HttpClientFactoryOptions [5][6]. This action is added to the HttpMessageHandlerBuilderActions collection [5][6]. Specifically, these actions are configured to assign a value to the PrimaryHandler property of the HttpMessageHandlerBuilder instance during the pipeline construction process [5][6]. There is no public API explicitly named SetPrimaryHandlerAction; this appears to be a misinterpretation of the internal mechanism where delegates (actions) are added to the HttpMessageHandlerBuilderActions list to perform the assignment of the PrimaryHandler property [5][6]. To configure the primary handler, you should use the provided extension methods on IHttpClientBuilder, such as [1][2]: services.AddHttpClient("my-client").ConfigurePrimaryHttpMessageHandler( => new HttpClientHandler { UseCookies = false }); The framework handles the underlying delegation to the HttpMessageHandlerBuilder automatically [5][6]. Direct interaction with HttpMessageHandlerBuilder.PrimaryHandler is typically reserved for advanced scenarios where you might be implementing a custom builder, though using the standard IHttpClientBuilder extensions is the recommended approach [3][9]. Citations:
🌐 Web query:
💡 Result: In.NET, HttpMessageHandlerBuilder.PrimaryHandler is an abstract property of the HttpMessageHandlerBuilder class that represents the primary HttpMessageHandler used by an HttpClient [1][2]. The primary handler is the innermost handler in an HttpClient's pipeline, responsible for actually performing the HTTP transport operations [3]. When using the IHttpClientFactory infrastructure, this handler acts as the final destination for an outbound request after it has passed through all configured additional handlers [2][3]. Key details regarding PrimaryHandler: 1. Functionality: It is the foundation of the handler chain. The HttpMessageHandlerBuilder.CreateHandlerPipeline method constructs the full pipeline by chaining additional handlers onto this primary handler [2][3]. 2. Configuration: You can configure the primary handler for a named HttpClient using the ConfigurePrimaryHttpMessageHandler extension method on IHttpClientBuilder [4][5]. 3. Default Behavior: If not explicitly configured, the default primary handler implementation varies by platform. In modern.NET versions, it is typically a SocketsHttpHandler, whereas on platforms like.NET Framework, it defaults to HttpClientHandler [5]. 4. Usage Note: There is no method named BuildPrimaryMessageHandler. The builder uses the PrimaryHandler property directly when constructing the handler pipeline [1][2]. Users should avoid reusing or caching instances of handlers provided to the builder, as the internal infrastructure expects them to be used in a specific lifecycle [1][6][3]. Top results: [1] https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Http/src/HttpMessageHandlerBuilder.cs [2] https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.http.httpmessagehandlerbuilder [3] https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.http.httpmessagehandlerbuilder.createhandlerpipeline [5] https://learn.microsoft.com/en-us/dotnet/core/compatibility/networking/9.0/default-handler Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
dotnet --version 2>/dev/null || true
dotnet --info 2>/dev/null | sed -n '1,40p' || true
echo
echo "Project SDK/framework references:"
cat -n Activout.RestClient.Newtonsoft.Json.Test/Activout.RestClient.Newtonsoft.Json.Test.csproj | sed -n '1,120p'
echo
echo "Find Microsoft.Extensions.Http package versions in project files:"
rg -n "Microsoft\.Extensions\.Http|<TargetFrameworks>|<TargetFramework>" -g '*.csproj' -g '*.props' -g '*.targets' . || true
echo
echo "Inspect installed/reference metadata from project assets if present:"
fd 'project.assets.json' . -x sh -c 'echo "--- $1"; cat "$1" | jq ".targets//empty" 2>/dev/null | jq -r --arg file "$1" "File:\($file)\n" | head -n 80' sh {}Repository: twogood/Activout.RestClient Length of output: 3094 🌐 Web query:
💡 Result: In the.NET Citations:
🌐 Web query:
💡 Result: In.NET 8, there is no method named BuildPrimaryMessageHandler within the HttpMessageHandlerBuilder class [1][2][3]. The HttpMessageHandlerBuilder abstraction uses the Build method to construct the HttpMessageHandler pipeline [2][4]. The configuration of the PrimaryHandler and AdditionalHandlers is managed through the options pattern in the Microsoft.Extensions.Http library [5][6]. Specifically, when you use extension methods like ConfigurePrimaryHttpMessageHandler, the configuration actions are stored in the HttpMessageHandlerBuilderActions collection (which is part of the HttpClient factory options) [5][6]. When an HttpClient is created, these actions are executed against an instance of the HttpMessageHandlerBuilder, setting its PrimaryHandler and AdditionalHandlers properties [5][2]. The pipeline itself is constructed using the protected static method CreateHandlerPipeline, which chains the PrimaryHandler and AdditionalHandlers together [7][2][3]. The logic mentioned in your query—where PrimaryHandler is set via builder actions—is consistent with how the IHttpClientFactory infrastructure populates the builder before calling Build [5][6]. Citations:
🌐 Web query:
💡 Result: In.NET 8, the PrimaryHandler is a property of the HttpMessageHandlerBuilder class [1], which is used by the HttpClientFactory infrastructure to construct the HttpMessageHandler pipeline [1]. While you can interact with the PrimaryHandler during the construction of an HttpClient, you do not typically set it by assigning a property named PrimaryHandler directly on a builder extension. Instead, you use the extension methods provided by the HttpClientBuilderExtensions class to configure it [2][3]. Key points regarding the PrimaryHandler and HttpClientBuilderExtensions in.NET 8: 1. HttpMessageHandlerBuilder.PrimaryHandler: This is an abstract property defined in the HttpMessageHandlerBuilder class [1]. It represents the primary HttpMessageHandler that sits at the base of the handler chain [1]. 2. Configuring the Primary Handler: You configure this handler via IHttpClientBuilder extension methods. The most common methods are: - ConfigurePrimaryHttpMessageHandler(Func): Allows you to provide a delegate that returns a new primary handler instance [4][3]. - ConfigurePrimaryHttpMessageHandler(Action<HttpMessageHandler, IServiceProvider>): Allows you to configure an existing primary handler [5][6]. - UseSocketsHttpHandler(Action<SocketsHttpHandler, IServiceProvider>): A specialized extension for configuring a SocketsHttpHandler as the primary handler [4][3]. 3. Internal Implementation: Internally, these extension methods register configuration actions with the HttpClientFactory [7]. When the HttpClientFactory builds the handler, it populates an HttpMessageHandlerBuilder instance [1], and the actions you registered are invoked to set or modify the PrimaryHandler property on that builder [7][1]. If you are looking at code similar to builder.PrimaryHandler =..., it is likely occurring inside a internal callback or a custom implementation of an extension method that has access to the HttpMessageHandlerBuilder instance being used during the pipeline construction process [7][8][1]. For standard application code, you should rely on the public API methods like ConfigurePrimaryHttpMessageHandler to define your primary handler [5][9]. Citations:
🌐 Web query:
💡 Result: The class HttpMessageHandlerBuilder in the.NET runtime (including version 8.0.0) is an abstract class [1][2]. It defines the PrimaryHandler property as an abstract property, meaning it does not contain a private field named _primaryHandler or any concrete implementation of that property within the class itself [1][3]. The definition in HttpMessageHandlerBuilder.cs is: public abstract HttpMessageHandler PrimaryHandler { get; set; } As an abstract class, it provides the blueprint for building an HttpMessageHandler pipeline, but the actual state management (such as the backing field for the primary handler) is handled by concrete implementations provided by the infrastructure, not the base class itself [1][4]. The IHttpClientFactory infrastructure uses this builder to chain additional handlers with the primary handler at the end of the pipeline [4]. Citations:
🌐 Web query:
💡 Result: In the.NET 8.0.0 implementation of Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
for nuget in "$HOME/.nuget/packages/microsoft.extensions.http/8.0.1" "$(dirname "$(dirname "$(dirname "$(pwd)")")")/.nuget/packages/microsoft.extensions.http/8.0.1"; do
[ -d "$nuget" ] && { echo "FOUND $nuget"; find "$nuget" -maxdepth 3 -type f -printf '%p\n' | sort; }
done
pkg="$(find "$HOME/.nuget/packages/microsoft.extensions.http" -type f -path '*/8.0.1/**/*.dll' -o -path '*/8.0.1/**/*.pdb' 2>/dev/null | head -n 20)"
echo "PKG DLL/PDB candidates:"
echo "$pkg" | head -n 40
if command -v ildasm >/dev/null 2>&1; then
dll="$(find "$HOME/.nuget/packages/microsoft.extensions.http" -type f -path '*8.0.1/*.dll' | head -n 1 || true)"
if [ -n "${dll:-}" ]; then
echo
echo "ILDASM HttpMessageHandlerBuilder properties/methods:"
ildasm "$dll" 2>/dev/null | \
sed -n '/\.class Microsoft\.Extensions\.Http\.HttpMessageHandlerBuilder/,/^\.method/p' | head -n 200 || true
echo
echo "ILDASM HttpClientBuilderExtensions configure/set methods:"
ildasm "$dll" 2>/dev/null | rg -n "SetPrimaryHandler|ConfigurePrimaryHttpMessageHandler|PrimaryHandler" -C 4 || true
fi
fi
if command -v dotnet >/dev/null 2>&1; then
temp="$(mktemp -d)"
proj="$temp/proj.csproj"
cat >/tmp/Probe.cs <<'CS'
using System;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Http;
public static class Probe
{
public static void Main()
{
try
{
var ext = typeof(Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions);
foreach (var name in new[] { "SetPrimaryHandlerAction", "SetPrimaryHandlerActionInternal", "UseSocketsHttpHandlerInternal" })
{
foreach (var m in ext.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (m.Name.Contains("PrimaryHandler") || m.Name.Contains("SocketsHttpHandler"))
Console.WriteLine(m.Name + " | " + string.Join(", ", m.GetParameters().Select(p => string.Join(" ", p.ParameterType.Name, p.Name))));
}
}
foreach (var m in ext.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (!m.Name.Contains("Primary") && !m.Name.Contains("Action") && !m.Name.Contains("Handler") && !m.Name.Contains("Logging")) continue;
Console.WriteLine(m.Name + " | " + string.Join(", ", m.GetParameters().Select(p => p.ParameterType.Name + " " + p.Name)));
}
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType() + ": " + ex.Message);
}
}
}
CS
cat >"$proj" <<'EP'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="/tmp/Probe.cs" />
</ItemGroup>
</Project>
EP
dotnet run --project "$proj" 2>&1 | sed -n '1,160p'
fi
# Read relevant source from available NuGet package if DLLs absent but XML/docs present.
for xml in $HOME/.nuget/packages/microsoft.extensions.http/8.0.1/lib/net8.0/*.xml; do
[ -e "$xml" ] || continue
rg -n -C 6 "PrimaryHandler|ConfigurePrimaryHttpMessageHandler|SetPrimaryHandlerAction|UseSocketsHttpHandler" "$xml" || true
doneRepository: twogood/Activout.RestClient Length of output: 165 🌐 Web query:
💡 Result: In.NET 8, the Microsoft.Extensions.Http library maintains the HttpMessageHandlerBuilder.PrimaryHandler property as a read-write property [1][2]. This property is defined in the abstract HttpMessageHandlerBuilder class [1] and implemented by the DefaultHttpMessageHandlerBuilder [3]. The PrimaryHandler property allows you to get or set the primary HttpMessageHandler used by an HttpClient [1][4]. While it is a settable property, it is intended to be managed by the IHttpClientFactory infrastructure [1][2]. Explicitly setting this property outside of standard configuration scenarios (like testing or custom factory implementations) can lead to unpredictable behavior [1][2]. It is important to note that while.NET 8 introduced significant networking improvements [5], the shift in the default primary handler from HttpClientHandler to SocketsHttpHandler (which affects how you might configure or cast the handler) was a behavioral change introduced in.NET 9 [6][7]. In.NET 8, the default primary handler remained HttpClientHandler [6][7]. To configure the primary handler in a supported, fluent manner, it is recommended to use the ConfigurePrimaryHttpMessageHandler extension method on the IHttpClientBuilder [8][9]. Avoid relying on the "factory-default" implementation of the primary handler, as this is an internal detail that can change between versions [6][9]. If you need to modify an existing handler, use the overload of ConfigurePrimaryHttpMessageHandler that accepts an Action<HttpMessageHandler, IServiceProvider>, which allows you to inspect and configure the handler safely [5][6][7]. Citations:
🌐 Web query:
💡 Result: There is no specific method or property named SetPrimaryHandlerAction in the Microsoft.Extensions.Http namespace or the HttpMessageHandlerBuilder class [1][2][3]. It is highly likely you are referring to the configuration approach used with HttpClientFactoryOptions, where actions are added to the HttpMessageHandlerBuilderActions collection [4][5][6]. In modern.NET development, the recommended way to configure the primary handler is through extension methods on IHttpClientBuilder rather than manually manipulating the HttpMessageHandlerBuilder [7][8][9]. Key points regarding HttpMessageHandlerBuilder: 1. PrimaryHandler Property: The HttpMessageHandlerBuilder class contains a PrimaryHandler property of type HttpMessageHandler, which gets or sets the primary handler (the innermost handler responsible for transport) [1][2][3]. 2. Configuration via Options: You can configure this by adding an action to the HttpMessageHandlerBuilderActions collection within HttpClientFactoryOptions [4][5]. This is often done using service configuration, though it is considered a lower-level, more manual approach [4][5]. 3. Recommended Approaches: Instead of manually accessing HttpMessageHandlerBuilder, use the following extension methods provided by Microsoft.Extensions.DependencyInjection [7][8]: - ConfigurePrimaryHttpMessageHandler: Use this to explicitly configure the primary handler (e.g., to set SSL options, proxy settings, or cookies) [7][8][9]. - ConfigureAdditionalHttpMessageHandlers: Use this to add DelegatingHandlers to the pipeline [7]. 4. Deprecation: Methods that explicitly required an Action (such as the method ConfigureHttpMessageHandlerBuilder) have been marked as obsolete, and developers are encouraged to use the more specific ConfigurePrimaryHttpMessageHandler or ConfigureAdditionalHttpMessageHandlers methods instead [10][6]. If you were looking for a way to set the handler, you should prefer: services.AddHttpClient("MyClient").ConfigurePrimaryHttpMessageHandler( => new SocketsHttpHandler {... }); [8] Citations:
🌐 Web query:
💡 Result: In the.NET runtime (specifically within Citations:
🌐 Web query:
💡 Result: In the.NET runtime repository, specifically within the Microsoft.Extensions.Http library, HttpClientBuilderExtensions.cs provides extension methods for configuring HttpClient instances via IHttpClientBuilder [1][2]. Regarding the specific components you mentioned: 1. ConfigurePrimaryHttpMessageHandler: This is a set of extension methods on IHttpClientBuilder that allows you to specify the primary HttpMessageHandler used by an HttpClient [1][3][4]. The primary handler is the innermost handler in the message handler pipeline, responsible for actually sending the request on the wire [5][6][7]. 2. AddPrimaryHttpMessageHandlerAction: This does not exist as a public API or a single named method in HttpClientBuilderExtensions. Instead, the configuration of the primary handler is achieved by adding actions to the HttpMessageHandlerBuilderActions collection within the internal configuration options of the IHttpClientBuilder [3][8]. When you call ConfigurePrimaryHttpMessageHandler, it registers a delegate that executes when the HttpMessageHandlerBuilder is built, which in turn sets the PrimaryHandler property on the builder [3][6]. 3. PrimaryHandler: This refers to the PrimaryHandler property of the HttpMessageHandlerBuilder class [6]. The IHttpClientFactory infrastructure uses this property to determine which handler acts as the base for the HTTP pipeline [5][7]. When you use ConfigurePrimaryHttpMessageHandler, you are effectively providing a factory function or a configuration delegate that the builder uses to set this PrimaryHandler [3]. In.NET 8.0 and later, the default primary handler is typically SocketsHttpHandler [5]. If you need to customize settings (like UseCookies or ClientCertificates), you can use ConfigurePrimaryHttpMessageHandler to either instantiate a new handler or configure the existing one [5]. Top results: [1][3][5][6] Citations:
Do not discard configured primary handlers.
🤖 Prompt for AI Agents |
||
|
|
||
| public override IList<DelegatingHandler> AdditionalHandlers { get; } = new List<DelegatingHandler>(); | ||
|
|
||
|
|
||
| public override HttpMessageHandler Build() | ||
| { | ||
| return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: twogood/Activout.RestClient
Length of output: 358
🏁 Script executed:
Repository: twogood/Activout.RestClient
Length of output: 7567
Align the SDK configuration to the .NET 9 baseline.
Activout.RestClient.Newtonsoft.Json.Test.csprojand this package reference are currently on the .NET 8 baseline. Migrate<TargetFramework>tonet9.0, updateMicrosoft.Extensions.Httpto9.0.x, and apply the same configuration baseline to the related .NET/.csproj configuration files.🤖 Prompt for AI Agents
Source: Coding guidelines