.NET 6+, typed HttpClient HttpMessageHandler not fired

Nayan Choudhary 0 Reputation points Microsoft Employee
2023-12-29T10:04:14.7233333+00:00

I am facing similar issue like mentioned here: https://learn.microsoft.com/en-us/answers/questions/588289/typed-client-addhttpmessagehandler-not-firing

I created a brand new project and it contains WeatherForecastController with a HttpClient injected. The problem still is, that the delegating handler is not invoked.

Upon debugging the code, I observed that the flow goes here, where the name (based on type) is not used by HttpClient factory. Maybe it is intentional, but I don't see the delegate handler being invoked anyway.

I am sharing the code below.

Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMvcCore();
builder.Services.AddHttpClient<WeatherForecastController>().AddHttpMessageHandler(() => new MyDelegateHandler());

var app = builder.Build();
app.MapControllers();
app.Run();

public sealed class MyDelegateHandler : DelegatingHandler {
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        // THIS IS WHAT I EXPECT TO BE EXECUTED!!!!
        Console.WriteLine("Hello from MyDelegateHandler!");
        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
    }
}

// Model
public sealed record WeatherForecast(DateTime Date, int TemperatureC, string? Summary) {
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Controller

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[ApiController]
[Route("[controller]")]
public sealed class WeatherForecastController : ControllerBase {
    private readonly HttpClient _httpClient;
    public WeatherForecastController(HttpClient httpClient) => _httpClient = httpClient;

    [HttpGet]
    public async Task<IEnumerable<WeatherForecast>> GetAsync() {
        using var r = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://www.google.com")).ConfigureAwait(false);
        return Array.Empty<WeatherForecast>();
    }
}

This is the code of the typed http client extension AddHttpClient

public static IHttpClientBuilder AddHttpClient<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TClient>(
	this IServiceCollection services, Action<HttpClient> configureClient)
	where TClient : class
{
    //...

    // Notice the name of the type is not used here
	AddHttpClient(services);

    // Now you extract the name, but the factory is already mapped to default unnamed client.
	string name = TypeNameHelper.GetTypeDisplayName(typeof(TClient), fullName: false);
	var builder = new DefaultHttpClientBuilder(services, name);
	builder.ConfigureHttpClient(configureClient);
	builder.AddTypedClientCore<TClient>(validateSingleType: true);
	return builder;
}

Kindly help me understand what is wrong or missing here?

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,227 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Zhi Lv - MSFT 32,036 Reputation points Microsoft Vendor
    2024-01-01T08:10:41.2733333+00:00

    Hi @Nayan Choudhary

    From your description, it seems that you want to add the HttpMessageHandler to the specify controller. In this scenario, you can refer to the following code:

    In the Program.cs file:

                builder.Services.AddControllersWithViews();
    
                // Register the delegatehandler
                builder.Services.AddTransient<MyDelegateHandler>();
    
                // Add HttpClient and configure a specific HttpMessageHandler for a controller
                builder.Services.AddHttpClient<HomeController>("YourHttpClientName", client =>
                {
                    // Configure your HttpClient settings here, if needed
                    client.BaseAddress=new Uri("https://localhost:7079/");
                })
                .AddHttpMessageHandler<MyDelegateHandler>(); // Add your custom HttpMessageHandler
    

    In the HomeController: Use the HttpClientFactory to create httpclient.

        public class HomeController : Controller
        { 
            private readonly IHttpClientFactory _httpClientFactory;
             
            public HomeController(IHttpClientFactory httpClientFactory)
            {
                _httpClientFactory = httpClientFactory;
            }
            public async Task<IActionResult> Index()
            {
                var _client = _httpClientFactory.CreateClient("YourHttpClientName"); 
                using (HttpResponseMessage response = await _client.GetAsync("/api/Values"))
                {
                    try
                    {
                        var result = await response.Content.ReadAsStringAsync();
                        return Ok(result);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Failed to read content");
    
                        return Ok("Failed");
                    }
                }
            }
    

    In the DelegatingHandler:

        public sealed class MyDelegateHandler : DelegatingHandler
        {
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                // THIS IS WHAT I EXPECT TO BE EXECUTED!!!!
                Console.WriteLine("Hello from MyDelegateHandler!");
                return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
            }
        }
    

    After that, the result as below:

    image2

    Reference: Make HTTP requests using IHttpClientFactory in ASP.NET Core

    Update:

    Besides, you can also refer to the following sample code to use a typed HttpClient.

    MyRepositoryClient.cs file:

        public interface IMyRepositoryClient
        {
            Task<string> GetAsync();
        }
        public class MyRepositoryClient : IMyRepositoryClient
        {
            private readonly HttpClient _client;
            private readonly ILogger<MyRepositoryClient> _logger; 
            public MyRepositoryClient(HttpClient client, ILogger<MyRepositoryClient> logger)
            {
                _client = client;
                _logger = logger;
            } 
            public async Task<string> GetAsync()
            {
                using (HttpResponseMessage response = await _client.GetAsync("/api/Values"))
                {
                    try
                    {
                        return await response.Content.ReadAsStringAsync();
                    }
                    catch (Exception e)
                    {
                        _logger.LogError(e, "Failed to read content");
                        return null;
                    }
                }
            }
        }
    

    MyDelegateHandler.cs

        public sealed class MyDelegateHandler : DelegatingHandler
        {
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                //  
                Console.WriteLine("Hello from MyDelegateHandler!");
                return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
            }
        }
    

    Program.cs file:

                // Register the delegatehandler
                builder.Services.AddTransient<MyDelegateHandler>();
                builder.Services.AddTransient<IMyRepositoryClient, MyRepositoryClient>();
                builder.Services.AddHttpClient<IMyRepositoryClient, MyRepositoryClient>()
                    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost:7079/"))
                    .AddHttpMessageHandler<MyDelegateHandler>();
    

    HomeController:

        public class HomeController : Controller
        { 
            private readonly IMyRepositoryClient _myclient;
             
            public HomeController(IMyRepositoryClient httpClientFactory)
            {
                _myclient = httpClientFactory;
            }
            public async Task<IActionResult> Index()
            {
                var result = await _myclient.GetAsync(); 
                 
                return Ok(result); 
            }
    

    The result as below:

    image2


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Dillion


  2. Nayan Choudhary 0 Reputation points Microsoft Employee
    2024-02-22T05:31:10.0933333+00:00

    I have figured out the issue. I cannot register a controller, because it will be replaced by the controller-auto-registration algo. This is why my registration, with HttpMessageHandler delegate, wasn't firing.

    builder.Services.AddHttpClient<WeatherForecastController>().AddHttpMessageHandler(() => new MyDelegateHandler()); 
    

    The right way is to not inject HttpClient in controller. Create a service interface and inject HttpClient there. Inject this service in controller.

    builder.Services.AddHttpClient<HttpClientService>()
        .AddHttpMessageHandler(() => new MyAuthenticationHandler());
    
    public sealed class HttpClientService {
        private readonly HttpClient _httpClient;
        public HttpClientService(HttpClient httpClient) {
            _httpClient = httpClient;
        }
        public HttpClient HttpClient => _httpClient;
    }
    
    
    [ApiController]
    [Route("[controller]")]
    public sealed class WeatherForecastController : ControllerBase {
        private readonly HttpClientService _clientService;
        public WeatherForecastController(HttpClientService clientService) => _clientService = clientService;
    
        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> GetAsync() {
            using var r = await _clientService.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://www.google.com")).ConfigureAwait(false);
            return Array.Empty<WeatherForecast>();
        }
    }
    

    Now, the HttpMessageHandler delegate will fire correctly.

    0 comments No comments