paint-brush
How to Work With Request Id in .NET Applicationsby@vdolzhenko
3,049 reads
3,049 reads

How to Work With Request Id in .NET Applications

by Viktoria DolzhenkoOctober 26th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

When building systems, developers face common problems that they use in many of their applications. In this article I will show how to work with Request Id, we will create a middleware for reading it and make it available to all chat programs for the purpose of sending it to child requests and logging.
featured image - How to Work With Request Id in .NET Applications
Viktoria Dolzhenko HackerNoon profile picture

When creating systems, developers are faced with common problems that they use in many of their applications. In this article, I will touch on one of these problems and show how I solve it.

Request Id

Most modern systems are built on microservice architecture. This means that a company can have many services, each of which is horizontally scalable and most likely has several instances. Thus, a request from a client may pass through many parts of the application before receiving a response.


As a result, developers have a natural need to track the entire chain of actions occurring with the client’s request. To do this, a single Request Id comes to their aid.


Request id serves the following purposes:


  • Quickly search for logs related to a specific user query. Especially useful when finding a bug
  • To trace services. It is useful to know which of the system services processes requests slowly


Typically, the Request Id is generated by the ingress controller of your system and then passed into all requests through headers.



Let's imagine that we are creating one of the backend systems. Our task is to read the Request Id from the request header and send it in the request header to the next system; we also need to use it in the logs and send it to the tracer, in general, it should be easily accessible from any class (May the DI be with you).


Let's start implementation. To start, we need a class in which we will store our Request Id.


public class SessionData
{
   private string _requestId = Guid.NewGuid().ToString();

   public string RequestId
   {
       get => _requestId;

       set
       {
           if (!string.IsNullOrEmpty(value))
               _requestId = value;
       }
   }
}


Here, by default, we fill in the RequestId; this is necessary if we have an application without an API, but with background jobs. That is when we are the initiators of the request. We also ensure that RequestId is never empty.


Next, we need to populate this class and ensure that this instance is used throughout the entire request processing time. And here, DI will come to our aid. We will simply add it as a Score to our service collection, then we will receive a new class with every request to our service.


services.AddScoped<SessionData>();


Now, we need to read the Request Id from the header. For this, we will use Middleware.


public class RequestIdMiddleware
{
   private readonly RequestDelegate _next;

   public RequestIdMiddleware(RequestDelegate next)
   {
       _next = next;
   }

   public async Task InvokeAsync(HttpContext context, SessionData sessionData)
   {
       sessionData.RequestId = context.Request.Headers["X-Request-Id"];
       await _next(context);
   }
}


Fine. Now, let's connect our middleware:


app.UseMiddleware<RequestIdMiddleware>();


To test the work, I will write a simple controller that will return our Request Id and call a service method that will print the Request Id.


[ApiController]
[Route("request_id")]
public class RequestIdController: Controller
{
   private readonly SessionData _sessionData;
   private readonly RequestIdService _requestIdService;

   public RequestIdController(
       SessionData sessionData,
       RequestIdService requestIdService)
   {
       _sessionData = sessionData;
       _requestIdService = requestIdService;
   }

   [HttpGet]
   public ActionResult<string> Get()
   {
       _requestIdService.PrintRequestId();
       return _sessionData.RequestId;
   }
}



And here is our service. Don't forget to add it to the IServiceCollection:


public class RequestIdService
{
   private readonly SessionData _sessionData;
   public RequestIdService(SessionData sessionData)
   {
       _sessionData = sessionData;
   }
   public void PrintRequestId()
   {
       Console.WriteLine($"RequestIdService request_id: {_sessionData.RequestId}");
   }
}


When calling a service with header X-Request-Id = SomeId. Our RequestId SomeId is returned to us + the line RequestIdService request_id: SomeId is displayed in the console.


Super. Now, let’s add it to the header when calling another service. To send messages, I created a helper class HttpSendHelper.


public class HttpSendHelper
{
   private readonly HttpClient _httpClient;
   private readonly SessionData _sessionData;

   public HttpSendHelper(
       HttpClient httpClient,
       SessionData sessionData)
   {
       _httpClient = httpClient;
       _sessionData = sessionData;
   }

   public async Task<string> GetRequestAsync(string url, CancellationToken cancellationToken)
   {
       using var request = new HttpRequestMessage(HttpMethod.Get, url);
       request.Headers.Add("X-Request-Id", _sessionData.RequestId);

       var response = await _httpClient.SendAsync(request, cancellationToken);
       string stringResult = await response.Content.ReadAsStringAsync(cancellationToken);

       return stringResult;
   }
}


Then I wrote a service that calls the method of our previously written controller. Let's not forget to add it to the IServiceCollection.


public class ExternalApiService
{
   private readonly HttpSendHelper _httpSendHelper;

   public ExternalApiService(HttpSendHelper httpSendHelper)
   {
       _httpSendHelper = httpSendHelper;
   }

   public Task<string> GetRequestIdAsync(CancellationToken cancellationToken)
   {
       return _httpSendHelper.GetRequestAsync("http://localhost:5000/request_id", cancellationToken);
   }
}


Let's add another method to our controller:


[HttpGet("external")]
public Task<string> GetExternal(CancellationToken cancellationToken)
{
   return _externalApiService.GetRequestIdAsync(cancellationToken);
}


Now, we need to run our application twice. And call a new method.