How to use correlation IDs in ASP.NET Core MVC

Capture and pass correlation IDs in ASP.NET Core MVC 5 to track HTTP requests that flow through multiple back-end services.

How to use correlation IDs in ASP.NET Core MVC
Thinkstock

Suppose you have implemented an application based on a microservices architecture. In a microservices-based application, you’ll typically have a conglomeration of several services. One of the significant benefits of microservices architecture is that each service is built, deployed, and maintained independently, promoting high scalability.

But let’s say something goes wrong. How would you identify, by looking at the logs, where the request failed? Your log files might include hundreds of thousands or perhaps millions of log messages. Hence searching through the log entries would be a daunting task. This is where correlation IDs come to the rescue.

In a previous article here, I talked about the basics of correlation IDs and how you can work with them in ASP.NET Web API. In this article we’ll examine how we can work with correlation IDs in ASP.NET Core.

To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here.

Create an ASP.NET Core MVC project in Visual Studio 2019

First off, let’s create an ASP.NET Core project in Visual Studio 2019. Following these steps will create a new ASP.NET Core MVC 5 project in Visual Studio 2019.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “ASP.NET Core Web App (Model-View-Controller)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window, specify the name and location for the new project.
  6. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
  7. Click Next.
  8. In the “Additional Information” window shown next, select .NET 5.0 as the target framework from the drop-down list at the top. Leave the “Authentication Type” as “None” (default).
  9. Ensure that the check boxes “Enable Docker,” “Configure for HTTPS,” and “Enable Razor runtime compilation” are unchecked as we won’t be using any of those features here.
  10. Click Create.

Next create a new controller in this project — let it be named ValuesController (that’s the default name). We’ll use this ASP.NET Core MVC project and controller to work with correlation IDs in the subsequent sections of this article.

What are correlation IDs? Why are they useful?

Correlation IDs are unique identifiers that enable you to correlate several micro tasks to a single macro action. Ensuring that each response has a unique ID enables tracking, monitoring, and debugging of each request.

By keeping track of each request you can determine where a particular request has failed from the logs. Because various service components may be involved in the execution of the request, you’ll need a method to connect all of these service components to the request; i.e., you will need a way to tie all of these service components to the request.

Capture and log correlation IDs using middleware

In ASP.NET Web API you could use message handlers to implement correlation IDs. However, in ASP.NET Core MVC 5 you don’t have message handlers. Therefore to implement correlation IDs in ASP.NET Core you must take advantage of a middleware.

Create a class named CustomMiddleware in a file named CustomMiddleware.cs in your project. Now enter the following code in there.

public class CustomMiddleware
    {
        private const string CorrelationIdHeaderKey = "X-Correlation-ID";
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;
        public CustomMiddleware(RequestDelegate next,
        ILoggerFactory loggerFactory)
        {
            _next = next ?? throw new ArgumentNullException(nameof(next));
            _logger = loggerFactory.CreateLogger<CustomMiddleware>();
        }
        public async Task Invoke(HttpContext httpContext)
        {
            string correlationId = null;
            if (httpContext.Request.Headers.TryGetValue(
            CorrelationIdHeaderKey, out StringValues correlationIds))
            {
                correlationId = correlationIds.FirstOrDefault(k =>
                k.Equals(CorrelationIdHeaderKey));
                _logger.LogInformation($"CorrelationId from Request Header:
                {correlationId}");
            }
            else
            {
                correlationId = Guid.NewGuid().ToString();
                httpContext.Request.Headers.Add(CorrelationIdHeaderKey,
                correlationId);
                _logger.LogInformation($"Generated CorrelationId:
                {correlationId}");
            }
            httpContext.Response.OnStarting(() =>
            {
                if (!httpContext.Response.Headers.
                TryGetValue(CorrelationIdHeaderKey,
                out correlationIds))
                    httpContext.Response.Headers.Add(
                    CorrelationIdHeaderKey, correlationId);
                return Task.CompletedTask;
            });
            await _next.Invoke(httpContext);
        }
    }

The CustomMiddleware class logs the correlation ID if it is found in the request header. If a correlation ID is not available in the request header, a new correlation ID is generated and added to the request and response headers. Note that a correlation ID here is a GUID — a globally unique identifier. (For an example of logging data to a text file, you might check out this article.) 

Pass correlation IDs to downstream services

In a typical microservices-based application, you might have several downstream services for handling a request. Correlation IDs provide a consistent approach to tracking requests and analyzing any problems you might have. For distributed tracing in such applications, you need correlation IDs and the ability to pass these IDs to the downstream services.

The following piece of code illustrates how you can pass correlation IDs to the downstream services.

public async Task<IActionResult> Index()
{
      var httpClient = new HttpClient();           
      var request = new HttpRequestMessage(HttpMethod.Get,
      "http://localhost:35532/api/values");
      request.Headers.Add("X-Correlation-ID",
      HttpContext.Request.Headers["X-Correlation-ID"].ToString());
      var response = await httpClient.SendAsync(request);
      string str = await response.Content.ReadAsStringAsync();
      List<string> data = JsonSerializer.Deserialize
      <List<string>>(str);
      return View();
}

When you run the application, you’ll be able to see a correlation ID in the response header of the first request, i.e., when the HttpGet action method of the HomeController is called. This is illustrated in Figure 1 below.

correlation ids 01 IDG

Figure 1.

The application has another call, this one for the ValuesController. Figure 2 below shows the correlation ID for this request in the request header. Note that the correlation IDs of the two calls are the same, meaning that both calls are part of the same request.

correlation ids 02 IDG

Figure 2.

Because requests might flow through many middlewares across multiple services, tracking them using correlation IDs is the only way to identify and diagnose errors that might sneak into these middleware systems. Rather than hard code the string constants used in the code examples given in this article, you should use a separate class where you can store constants such as “X-Correlation-ID.”

Copyright © 2021 IDG Communications, Inc.

How to choose a low-code development platform