In un'architettura basata su microservices, in cui una chiamata può facilmente coinvolgere diversi servizi, l'utilizzo di un correlation Id rende le operazioni di debug più semplici.
L'idea tutto sommato è piuttosto banale: si stabilisce un header convenzionale (per esempio x-cid); il primo servizio a rispondere alla richiesta genera un codice univoco e lo passa in cascata, tramite lo stesso header, a tutti gli altri servizi chiamati.

In questo modo ognuno potrà loggarlo e, successivamente, utilizzarlo - appunto - per correlare tra di loro tutte le chiamate, le risposte e i log dei singoli servizi.
OWIN si dimostra un'architettura eccellente ed estremamente versatile per gestire questo tipo di requisiti, e la maniera più elegante per realizzare il supporto al correlation Id è tramite un custom middleware. Basta creare una classe che esponga un metodo Invoke come la seguente:
public class CorrelationIdMiddleware { private RequestDelegate _next; public CorrelationIdMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { string correlationId = context.Request.Headers["x-cid"].ToString(); if (string.IsNullOrWhiteSpace(correlationId)) correlationId = Guid.NewGuid().ToString(); context.Response.Headers.Add("x-cid", new StringValues(correlationId)); await _next.Invoke(context); } }
Il parametro RequestDelegate nel costruttore verrà passato dal runtime, e rappresenta il prossimo middleware nel flusso di esecuzione. Il metodo Invoke riceve il context della richiesta corrente e non fa altro che valutare se gli header della richiesta contengono il correlation Id, e in caso negativo, generarne uno nuovo.
Successivamente lo include negli header della risposta.
Per poter agevolmente registrare questo componente, possiamo creare un extension method come il seguente:
public static class CorrelationIdExtensions { public static void AddCorrelationId(this IApplicationBuilder app) { app.UseMiddleware<CorrelationIdMiddleware>(); } }
In questo modo, potremmo facilmente aggiungerlo alla pipeline di richiesta nella classe Startup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.AddCorrelationId(); // ... altro codice ... }
Quanto fatto finora è già perfettamente funzionante, e se provassimo a eseguire l'applicazione ci renderemmo conto di come sia già in grado di
- generare un identificativo se non presente nella richiesta;
- restituire il correlation id tra gli header della risposta.
Ciò che manca è un modo semplice per risalire all'Id corrente da un controller o da un servizio quando, in procinto di invocare un web service, ne abbiamo bisogno per passarlo tra gli header della nostra richiesta.
Si tratta di un problema molto semplice da risolvere, creando una classe CorrelationIdProvider che funga da contenitore, e registrandola come Scoped, cioè un'istanza per Request:
public class CorrelationIdProvider { public string CorrelationId { get; set; } } public class Startup { // ... altro codice ... public void ConfigureServices(IServiceCollection services) { // ... altro codice ... services.AddScoped<CorrelationIdProvider>(); } }
Questo oggetto può essere poi recuperato dal nostro middleware e usato per salvare temporaneamente il correlation Id generato. Il metodo Invoke visto in precedenza, diverrà:
public async Task Invoke(HttpContext context) { string correlationId = context.Request.Headers["x-cid"].ToString(); if (string.IsNullOrWhiteSpace(correlationId)) correlationId = Guid.NewGuid().ToString(); context.Response.Headers.Add("x-cid", new StringValues(correlationId)); var provider = (CorrelationIdProvider)context.RequestServices.GetService(typeof(CorrelationIdProvider)); provider.CorrelationId = correlationId; await _next.Invoke(context); }
A questo punto, utilizzarlo in un controller o in ogni altro servizio dell'applicazione è solo una questione di aggiungerlo tra i parametri del costruttore:
public HomeController(CorrelationIdProvider cidProvider) { _cidProvider = cidProvider; } public async Task<IActionResult> Index() { var client = new HttpClient(); var request = new HttpRequestMessage() { RequestUri = new Uri("http://..."), Method = HttpMethod.Get }; request.Headers.Add("x-cid", _cidProvider.CorrelationId); var response = await client.SendAsync(request); // ... }
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Le novità di Entity Framework (Core) 7
Agenda di #devconf22 del 26/05 quasi al completo! Ce n'è per tutti i gusti: #dotnet, #aspnetcore, #blazor, #terraform, #githubAltre informazioni e iscrizioni su => https://aspit.co/devconf-22
Gestire dati sensibili nella configurazione in ASP.NET Core
Migrare un progetto ASP.NET Core da .NET 6 a .NET 7
DateOnly e TimeOnly in .NET: e io che ci faccio?
Usare domini personalizzati con Azure Container App
Definire la durata dell'output cache in ASP.NET Core 7
Taggare la output cache in base al routing in ASP.NET Core
Leggere la configurazione da Azure KeyVault con logica di retry in ASP.NET Core
Personalizzare le richieste con i rule set di Azure Front Door
I più letti di oggi
- DateOnly e TimeOnly in .NET: e io che ci faccio?
- .NET 7 Live Q&A
- Catturare la telemetria degli eventi di output cache in ASP.NET Core
- Nasce DOpsItalia.com: tutto su DevOps e container https://aspit.co/bw3 di @dbochicchio #aspitalia
- il 15/12 torna #netconfit per festeggiare il lancio di #dotnet7.una track live a Milano e 2 in streaming, per un evento ibrido da non perdere!iscrizioni e call for paper su => https://aspit.co/netconf-22
- Utilizzare la modalità serverless con Azure Cosmos DB
- Abilitare automaticamente Dependabot in tutti i repository di una organizzazione su GitHub
- Community Days 'Tour Edition' 2010 - Segrate (MI)
- Rilasciata la preview 1 di ASP.NET AJAX 4.0
- Nuova versione per jQuery e prima alpha per jQuery Mobile