Mastering DelegatingHandler in .NET: Logging HTTP Requests with HttpClient
A practical guide to DelegatingHandler in .NET,Learn how to log HTTP requests and responses using a custom handler.
When working with APIs in .NET, HttpClient
is the go-to utility for making HTTP calls. But what if you want to log requests and responses? Or add custom headers like correlation IDs or tokens? That’s where DelegatingHandler
comes in — a powerful but often underutilized tool in .NET.
In this article, we’ll explore DelegatingHandler
with a hands-on example: building a custom LoggingHandler
to log every request and response made by HttpClient
.
🧠 What is a DelegatingHandler
?
A DelegatingHandler
is a special type of message handler in the HttpClient
pipeline that lets you intercept, inspect, and manipulate HTTP requests and responses — just like middleware does in ASP.NET Core.
You can chain multiple DelegatingHandler
s together to handle cross-cutting concerns like:
Logging
Authentication
Tracing and Correlation IDs
Retry logic
Response transformation
Mocking in tests
🔧 Let’s Build: Logging with DelegatingHandler
We’ll build a .NET 6+ API that fetches posts from a public API (https://jsonplaceholder.typicode.com/posts
) and logs the HTTP call using a custom LoggingHandler
.
✅ Step 1: Create the LoggingHandler
Create a new class called LoggingHandler.cs
:
public class LoggingHandler : DelegatingHandler
{
private readonly ILogger<LoggingHandler> _logger;
public LoggingHandler(ILogger<LoggingHandler> logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
_logger.LogInformation("➡️ Sending Request: {Method} {Uri}", request.Method, request.RequestUri);
if (request.Content != null)
{
var requestBody = await request.Content.ReadAsStringAsync();
_logger.LogInformation("📤 Request Body: {Body}", requestBody);
}
var response = await base.SendAsync(request, cancellationToken);
_logger.LogInformation("⬅️ Received Response: {StatusCode}", response.StatusCode);
if (response.Content != null)
{
var responseBody = await response.Content.ReadAsStringAsync();
_logger.LogInformation("📥 Response Body: {Body}", responseBody);
}
return response;
}
}
✅ Step 2: Create the PostService
Now, let’s create a simple service that uses HttpClient
to call an external API.
public class PostService
{
private readonly HttpClient _httpClient;
public PostService(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("MyClient");
}
public async Task<string> GetPostsAsync()
{
var response = await _httpClient.GetAsync("https://jsonplaceholder.typicode.com/posts");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
✅ Step 3: Create the Controller
We’ll create an API controller that uses the PostService
.
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class PostController : ControllerBase
{
private readonly PostService _postService;
public PostController(PostService postService)
{
_postService = postService;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var result = await _postService.GetPostsAsync();
return Content(result, "application/json");
}
}
✅ Step 4: Configure in Program.cs
Here’s how you configure the whole pipeline in your Program.cs
(for .NET 6/7/8):
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
builder.Services.AddTransient<LoggingHandler>();
builder.Services.AddTransient<PostService>();
builder.Services.AddHttpClient("MyClient")
.AddHttpMessageHandler<LoggingHandler>();
var app = builder.Build();
// Configure the HTTP pipeline
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
🧪 Test the API
Run the application and visit:
https://localhost:{PORT}/api/post
You should receive a JSON response from the external API, and in the console you’ll see logs like:
➡️ Sending Request: GET https://jsonplaceholder.typicode.com/posts
⬅️ Received Response: 200
📥 Response Body: [{"userId":1,"id":1,"title":"..."}]
📦 Folder Structure
Here’s a suggested folder structure:
/MyApiProject
│
├── Handlers
│ └── LoggingHandler.cs
│
├── Services
│ └── PostService.cs
│
├── Controllers
│ └── PostController.cs
│
└── Program.cs
🔍 Real-World Use Cases
DelegatingHandler
isn’t just for logging. You can use it for:
✅ Adding authentication tokens
✅ Attaching correlation or trace IDs
✅ Handling retries with Polly
✅ Simulating APIs in tests
✅ Transforming or validating responses
🧠 Final Thoughts
DelegatingHandler
is one of those elegant design features in .NET that lets you build powerful and clean middleware-like logic for outgoing HTTP requests. If you're calling external APIs, it’s a must-know tool in your toolbox.
Start small with a logging handler, and then evolve your pipeline with authentication, telemetry, retry, and more — all cleanly separated from your core logic.
💬 Have you used DelegatingHandlers in production? Share your tips or use cases in the comments below!
📁 GitHub Example
👉 Full working code available at:
🔗 https://github.com/KanaiyaKatarmal/LoggingHTTPCalls
I hope you found this guide helpful and informative.
Thanks for reading!
If you enjoyed this article, feel free to share it and follow me for more practical, developer-friendly content like this.