Take advantage of a custom message handler to log request and response metadata in Web API for debugging, tracing, and inspection of the incoming and outgoing calls Credit: Thinkstock Like authentication, caching, and exception management, logging is a crosscutting concern – a function that affects the entire application – that should be centralized. We often log application data that may include the sequence of method calls or events, user actions, or even errors that may occur when the application executes. There are many logging frameworks you could take advantage of, but in this article we’ll focus on how we can log requests and responses in ASP.NET Web API. Logging requests and responses in Web API is helpful in debugging, tracing, and inspection of the incoming and outgoing service calls. By logging all requests and responses in one place, detecting problems in any requests and responses becomes easy. In this post, we will create a custom message handler to monitor and log requests and responses in Web API. The message handler will be used to intercept calls and log all requests and responses centrally in one place. Strategies to inject crosscutting concerns in Web API There are multiple ways to inject logging and other crosscutting concerns in Web API. One way is to create a custom ApiController class, or a base class for all of our controllers, and then override the ExecuteAsync method. Another way is to use a custom action filter. However, both of these strategies have their limitations. In the former case, we would have to ensure that all of our controllers extend the custom base controller class. In the latter, we would have to ensure that the filter is applied on all of the controllers we use. The best strategy in my opinion is to use a message handler because you write it only once and then register it in one place. Also, because the custom message handler will be called much earlier in the pipeline, i.e., even before the HttpControllerDispatcher, it is well suited to injecting crosscutting concerns. Incidentally, message handlers are classes that inherit the abstract HttpMessageHandler class. Hence, we will be taking advantage of a message handler to inject our custom logger in this post. If you want to build and execute the source code illustrated in this post, you should have Visual Studio up and running in your system. Also, you should have NLog installed. If you want to know how to install, configure, and use NLog, take a look at my article on NLog here. Building our customer logger for Web API Create a new Web API project in Visual Studio and save it with your desired name. We will be taking advantage of a custom delegating handler here to intercept the calls to the Web API. First off, let’s build a custom POCO class that will store all of the information from our requests and responses. public class LogMetadata { public string RequestContentType { get; set; } public string RequestUri { get; set; } public string RequestMethod { get; set; } public DateTime? RequestTimestamp { get; set; } public string ResponseContentType { get; set; } public HttpStatusCode ResponseStatusCode { get; set; } public DateTime? ResponseTimestamp { get; set; } } Now we’ll implement a custom class called LogHandler. This is essentially a message handler that extends the DelegatingHandler class. public class CustomLogHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return base.SendAsync(request, cancellationToken); } } The following code snippet shows how you can build request metadata. This method will be called from the SendAsync method of our custom message handler and will return an instance of the LogMetadata class. private LogMetadata BuildRequestMetadata(HttpRequestMessage request) { LogMetadata log = new LogMetadata { RequestMethod = request.Method.Method, RequestTimestamp = DateTime.Now, RequestUri = request.RequestUri.ToString() }; return log; } The next thing we need to do is update the log metadata instance with information from the response object. Here’s how this can be achieved. private LogMetadata BuildResponseMetadata(LogMetadata logMetadata, HttpResponseMessage response) { logMetadata.ResponseStatusCode = response.StatusCode; logMetadata.ResponseTimestamp = DateTime.Now; logMetadata.ResponseContentType = response.Content.Headers.ContentType.MediaType; return logMetadata; } Here is the complete source code of the custom message handler for your reference. public class CustomLogHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var logMetadata = BuildRequestMetadata(request); var response = await base.SendAsync(request, cancellationToken); logMetadata = BuildResponseMetadata(logMetadata, response); await SendToLog(logMetadata); return response; } private LogMetadata BuildRequestMetadata(HttpRequestMessage request) { LogMetadata log = new LogMetadata { RequestMethod = request.Method.Method, RequestTimestamp = DateTime.Now, RequestUri = request.RequestUri.ToString() }; return log; } private LogMetadata BuildResponseMetadata(LogMetadata logMetadata, HttpResponseMessage response) { logMetadata.ResponseStatusCode = response.StatusCode; logMetadata.ResponseTimestamp = DateTime.Now; logMetadata.ResponseContentType = response.Content.Headers.ContentType.MediaType; return logMetadata; } private async Task<bool> SendToLog(LogMetadata logMetadata) { // TODO: Write code here to store the logMetadata instance to a pre-configured log store... return true; } } Note that you need to write the necessary code to store the logMetadata instance shown in the SendToLog method to a pre-configured log target, i.e., a file or a database. I prefer using NLog to log this metadata. Again, you can refer to my article on NLog to see how this can be done. Registering the message handler To register the custom message handler you can take advantage of the Application_Start event in the Global.asax.cs file or the Register method of the WebApiConfig class. The following code snippet illustrates how you can register the handler using the Register method of the WebApiConfig class. public static void Register(HttpConfiguration config) { // Write your usual code here... config.MessageHandlers.Add(new CustomLogHandler()); } In this article we examined how we can log requests and responses in Web API using a custom message handler. Message handlers are an excellent way to inject crosscutting concerns into the Web API pipeline. Though we have other ways to inject logging into Web API, such as a custom ApiController class or a custom action filter, using a custom message handler is a simpler approach. You can feel free to tweak this implementation based on your requirements, e.g, to add more custom metadata. How to do more in ASP.NET and ASP.NET Core: How to use in-memory caching in ASP.NET Core How to handle errors in ASP.NET Web API How to pass multiple parameters to Web API controller methods How to log request and response metadata in ASP.NET Web API How to work with HttpModules in ASP.NET Advanced versioning in ASP.NET Core Web API How to use dependency injection in ASP.NET Core How to work with sessions in ASP.NET How to work with HTTPHandlers in ASP.NET How to use IHostedService in ASP.NET Core How to consume a WCF SOAP service in ASP.NET Core How to improve the performance of ASP.NET Core applications How to consume an ASP.NET Core Web API using RestSharp How to work with logging in ASP.NET Core How to use MediatR in ASP.NET Core How to work with session state in ASP.NET Core How to use Nancy in ASP.NET Core Understand parameter binding in ASP.NET Web API How to upload files in ASP.NET Core MVC How to implement global exception handling in ASP.NET Core Web API How to implement health checks in ASP.NET Core Best practices in caching in ASP.NET How to use Apache Kafka messaging in .NET How to enable CORS on your Web API When to use WebClient vs. HttpClient vs. HttpWebRequest How to work with Redis Cache in .NET When to use Task.WaitAll vs. Task.WhenAll in .NET Related content feature What is Rust? Safe, fast, and easy software development Unlike most programming languages, Rust doesn't make you choose between speed, safety, and ease of use. Find out how Rust delivers better code with fewer compromises, and a few downsides to consider before learning Rust. By Serdar Yegulalp Nov 20, 2024 11 mins Rust Programming Languages Software Development how-to Kotlin for Java developers: Classes and coroutines Kotlin was designed to bring more flexibility and flow to programming in the JVM. Here's an in-depth look at how Kotlin makes working with classes and objects easier and introduces coroutines to modernize concurrency. By Matthew Tyson Nov 20, 2024 9 mins Java Kotlin Programming Languages analysis Azure AI Foundry tools for changes in AI applications Microsoft’s launch of Azure AI Foundry at Ignite 2024 signals a welcome shift from chatbots to agents and to using AI for business process automation. By Simon Bisson Nov 20, 2024 7 mins Microsoft Azure Generative AI Development Tools news Microsoft unveils imaging APIs for Windows Copilot Runtime Generative AI-backed APIs will allow developers to build image super resolution, image segmentation, object erase, and OCR capabilities into Windows applications. By Paul Krill Nov 19, 2024 2 mins Generative AI APIs Development Libraries and Frameworks Resources Videos