Exploring Runtime HTTP Logging in .NET
Recently, I experimented with an automatic HTTP request logging library:HttpMataki.NET.
This library can capture HTTP request and response information without modifying the original application code, which makes it especially useful during development and debugging. Being able to inspect request and response details transparently is extremely convenient when diagnosing issues or researching process.
While reviewing its implementation, I noticed that it relies on two interesting .NET runtime techniques. I took this opportunity to explore and better understand how these mechanisms work under the hood.
DelegatingHandler
Before diving into more advanced runtime techniques, the first concept worth clarifying is DelegatingHandler.
A natural question comes up:
If we can log requests using middleware, why do we need DelegatingHandler at all?
To answer that, we need to distinguish between inbound requests and outbound requests in a .NET web application.
Inbound Requests: Handled by Kestrel
When an application receives an HTTP request:
Client → Kestrel → Middleware Pipeline → Controller
The request is processed by the web server (typically Kestrel) and flows through the middleware pipeline.
This is where we commonly implement:
- Logging
- Authentication
- Exception handling
- Metrics collection
Middleware operates on incoming requests.
Outbound Requests: Handled by HttpClient
However, when our application calls an external service:
Our App → HttpClient → External API
This is a completely different pipeline.
Middleware does not participate in outbound HTTP calls.
Instead, outbound requests are handled by HttpClient, and this is where DelegatingHandler comes in.
What is DelegatingHandler?
DelegatingHandler acts as a message handler in the HttpClient pipeline. It forms a responsibility chain around outbound HTTP requests.
You can think of it as:
- A wrapper around outgoing requests
- A way to intercept and modify requests
- A way to inspect responses
It allows you to log, modify, or short-circuit outbound HTTP traffic.
This is how the logging library is able to inspect:
- Requests sent via
HttpClient - Responses received from external services
Without modifying application business logic.
For detailed implementation, you can refer to the official documentation, but conceptually it behaves like a chain-of-responsibility pattern for outbound HTTP traffic.
Simple Demonstration
I created a minimal example to demonstrate where middleware and DelegatingHandler sit in the request lifecycle:
These examples clearly show the difference in execution position between inbound middleware processing and outbound HttpClient interception.
Harmony: Runtime Method Interception in .NET
The second technique I explored is Harmony, a runtime method patching library.
Harmony allows developers to modify the behavior of existing methods at runtime without changing the original source code. It works by intercepting method execution and injecting custom logic before, after, or even in place of the original implementation.
However, it is important to understand its scope and limitations.
Harmony can only operate within the same process. It does not:
- Inject code into other processes
- Modify unmanaged/native applications
- Change type structure (such as adding fields or extending enums)
In other words, Harmony modifies behavior — not structure.
A Simple Example
To better understand how it works, I created a minimal example demonstrating:example
Overall, Harmony is quite straightforward to use once you understand its parameter binding conventions and runtime patching model.
In this case, I encountered Harmony while reviewing the implementation of the Mataki library and took the opportunity to explore how it works under the hood.
While it is not a tool I would reach for in everyday application development, understanding its mechanics provides valuable insight into how runtime interception works in .NET.
Top comments (0)