From 264b8087f83c9f245cefd414f1ca4d57aaa0a9d6 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sat, 17 Oct 2026 23:57:30 +0200 Subject: [PATCH] Default User-Agent on HttpClient: net10.0, CreateApplicationBuilder, ConfigureHttpClientDefaults, on-the-wire tests --- ...owToSetDefaultUserAgentOnHttpClient.csproj | 6 +- .../Program.cs | 47 ++++++++++---- .../Tests/ProgramUnitTests.cs | 35 +++++++++- .../Tests/Tests.csproj | 14 ++-- .../Tests/UserAgentOnTheWireTests.cs | 64 +++++++++++++++++++ 5 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/UserAgentOnTheWireTests.cs diff --git a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient.csproj b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient.csproj index 0bb3be0f0d..21f31fb431 100644 --- a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient.csproj +++ b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient.csproj @@ -2,14 +2,14 @@ Exe - net8.0 + net10.0 enable enable - - + + diff --git a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/Program.cs b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/Program.cs index 0d5c2d1f34..67e0623b53 100644 --- a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/Program.cs +++ b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/HowToSetDefaultUserAgentOnHttpClient/Program.cs @@ -9,37 +9,60 @@ public static void Main(string[] args) { var host = BuildHost(); - var httpClientFactory = host.Services.GetService(); + var httpClientFactory = host.Services.GetRequiredService(); GetDefaultAgentOnHttpClient(httpClientFactory); + SetUserAgentOnHttpRequestMessage("CodeMazeDesktopApp/1.1"); + SetUserAgentOnHttpRequestMessage("CodeMazeDesktopApp", "1.1"); } public static IHost BuildHost() { - var builder = new HostBuilder() - .ConfigureServices(services => - { - services.AddHttpClient("CodeMazeHttpClient", client => - { - var productHeaderValue = new ProductHeaderValue(name: "CodeMazeDesktopApp", version: "1.1"); - var productInfoHeaderValue = new ProductInfoHeaderValue(productHeaderValue); + var builder = Host.CreateApplicationBuilder(); + + builder.Services.AddHttpClient("CodeMazeHttpClient", client => + { + var productHeaderValue = new ProductHeaderValue(name: "CodeMazeDesktopApp", version: "1.1"); + var productInfoHeaderValue = new ProductInfoHeaderValue(productHeaderValue); + + client.DefaultRequestHeaders.UserAgent.Add(productInfoHeaderValue); + }); + + return builder.Build(); + } + + public static IHost BuildHostWithUserAgentForAllClients() + { + var builder = Host.CreateApplicationBuilder(); - client.DefaultRequestHeaders.UserAgent.Add(productInfoHeaderValue); - }); + builder.Services.ConfigureHttpClientDefaults(httpClientBuilder => + { + httpClientBuilder.ConfigureHttpClient(client => + { + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("CodeMazeDesktopApp", "1.1")); }); + }); return builder.Build(); } public static string GetDefaultAgentOnHttpClient(IHttpClientFactory httpClientFactory) - { + { var httpClient = httpClientFactory.CreateClient("CodeMazeHttpClient"); return httpClient.DefaultRequestHeaders.UserAgent.ToString(); } + public static string SetUserAgentOnHttpRequestMessage(string userAgentValue) + { + var httpRequestMessage = new HttpRequestMessage(); + httpRequestMessage.Headers.Add("User-Agent", userAgentValue); + + return httpRequestMessage.Headers.UserAgent.ToString(); + } + public static string SetUserAgentOnHttpRequestMessage(string productName, string productVersion) { var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "/users/1234"); @@ -51,4 +74,4 @@ public static string SetUserAgentOnHttpRequestMessage(string productName, string return httpRequestMessage.Headers.UserAgent.ToString(); } -} \ No newline at end of file +} diff --git a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/ProgramUnitTests.cs b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/ProgramUnitTests.cs index 7170558ada..6ce359bfbd 100644 --- a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/ProgramUnitTests.cs +++ b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/ProgramUnitTests.cs @@ -5,6 +5,19 @@ namespace Tests; public class ProgramUnitTests { + [Fact] + public void WhenUserAgentStringIsProvided_ThenSetUserAgentOnHttpRequestMessageSetsTheHeader() + { + //Arrange + var userAgentValue = "CodeMazeDesktopApp/1.1"; + + //Act + var actualOutput = Program.SetUserAgentOnHttpRequestMessage(userAgentValue); + + //Assert + Assert.Equal(userAgentValue, actualOutput); + } + [Fact] public void WhenUserAgentPropertyIsProvided_ThenSetUserAgentOnHttpRequestMessageSetsTheHeader() { @@ -30,11 +43,29 @@ public void WhenHttpClientIsRegisteredWithHostBuilder_ThenHttpClientGetsDefaultU //Act var host = Program.BuildHost(); - var httpClientFactory = host.Services.GetService(); + var httpClientFactory = host.Services.GetRequiredService(); var actualOutput = Program.GetDefaultAgentOnHttpClient(httpClientFactory); //Assert Assert.Equal(expectedOutput, actualOutput); } -} \ No newline at end of file + + [Fact] + public void WhenClientDefaultsAreConfigured_ThenEveryClientGetsDefaultUserAgent() + { + //Arrange + var expectedOutput = "CodeMazeDesktopApp/1.1"; + + //Act + var host = Program.BuildHostWithUserAgentForAllClients(); + var httpClientFactory = host.Services.GetRequiredService(); + + var namedClient = httpClientFactory.CreateClient("AnyName"); + var defaultClient = httpClientFactory.CreateClient(); + + //Assert + Assert.Equal(expectedOutput, namedClient.DefaultRequestHeaders.UserAgent.ToString()); + Assert.Equal(expectedOutput, defaultClient.DefaultRequestHeaders.UserAgent.ToString()); + } +} diff --git a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/Tests.csproj b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/Tests.csproj index 179687640e..5117625127 100644 --- a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/Tests.csproj +++ b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,16 +10,20 @@ - - - - + + + + + + + + diff --git a/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/UserAgentOnTheWireTests.cs b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/UserAgentOnTheWireTests.cs new file mode 100644 index 0000000000..c5539b93a1 --- /dev/null +++ b/aspnetcore-features/HowToSetDefaultUserAgentOnHttpClient/Tests/UserAgentOnTheWireTests.cs @@ -0,0 +1,64 @@ +using HowToSetDefaultUserAgentOnHttpClient; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http.Headers; + +namespace Tests; + +public class UserAgentOnTheWireTests : IAsyncLifetime +{ + private WebApplication _app = null!; + private string _baseAddress = null!; + + public async Task InitializeAsync() + { + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseUrls("http://127.0.0.1:0"); + + _app = builder.Build(); + _app.MapGet("/user-agent", (HttpRequest request) => request.Headers.UserAgent.ToString()); + + await _app.StartAsync(); + _baseAddress = _app.Urls.First(); + } + + public async Task DisposeAsync() => await _app.DisposeAsync(); + + [Fact] + public async Task WhenPlainHttpClientCallsTheServer_ThenNoUserAgentArrives() + { + using var httpClient = new HttpClient(); + + var userAgent = await httpClient.GetStringAsync($"{_baseAddress}/user-agent"); + + Assert.Equal(string.Empty, userAgent); + } + + [Fact] + public async Task WhenNamedClientCallsTheServer_ThenServerReadsTheDefaultUserAgent() + { + var httpClientFactory = Program.BuildHost().Services.GetRequiredService(); + var httpClient = httpClientFactory.CreateClient("CodeMazeHttpClient"); + + var userAgent = await httpClient.GetStringAsync($"{_baseAddress}/user-agent"); + + Assert.Equal("CodeMazeDesktopApp/1.1", userAgent); + } + + [Fact] + public async Task WhenRequestSetsItsOwnUserAgent_ThenItReplacesTheDefault() + { + var httpClientFactory = Program.BuildHost().Services.GetRequiredService(); + var httpClient = httpClientFactory.CreateClient("CodeMazeHttpClient"); + + var request = new HttpRequestMessage(HttpMethod.Get, $"{_baseAddress}/user-agent"); + request.Headers.UserAgent.Add(new ProductInfoHeaderValue("CodeMazeReportJob", "2.0")); + + var response = await httpClient.SendAsync(request); + var userAgent = await response.Content.ReadAsStringAsync(); + + Assert.Equal("CodeMazeReportJob/2.0", userAgent); + } +}