Quando la nostra suite di servizi è utilizzata da terze parti, introdurre modifiche nella logica o nell'interfaccia è sempre piuttosto problematico, perchè si rischia di "rompere" del software funzionante di cui non abbiamo il controllo.
La soluzione che si adotta, allora, è quella di introdurre un numero di versione nell'URL, come parte del path o come parametro di query string, così che i nostri utilizzatori possano adeguare i client senza rischiare malfunzionamenti.
Dal nostro punto di vista, ossia di coloro che realizzano lo strato di servizi, il modo più naturale per gestire questa necessità è usare differenti namespace per i vari controller, come nell'immagine in basso.
Purtroppo questo scenario non è direttamente supportato da Web API, e quindi dobbiamo realizzare qualche piccola personalizzazione per riuscire a implementarlo. Prima di tutto, però, modifichiamo le regole di routing per introdurre un nuovo parametro per la versione e mapparlo su un URL simile a localhost/V1/Values/2.
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{version}/{controller}/{id}", defaults: new { id = RouteParameter.Optional, version = "V1" } );
Nel ciclo di vita della request di ASP.NET Web API, il componente che si occupa di selezionare il controller da utilizzare è IHttpControllerSelector. Per crearne una versione personalizzata, tuttavia, conviene partire da DefaultHttpControllerSelector e ridefinirne il metodo SelectController.
public class VersionedControllerSelector : DefaultHttpControllerSelector { private readonly Lazy<ConcurrentBag<VersionedController>> _versionedControllers; ... public override HttpControllerDescriptor SelectController( HttpRequestMessage request) { ... } }
Questo oggetto, contiene al suo interno una collection di VersionedController - è importante che sia una ConcurrentBag visto che questa classe deve essere thread safe - che viene inizializzata al primo utilizzo. VersionedController è una semplice classe che ci serve per tener traccia di tutti i controller e le versioni presenti nella solution.
private class VersionedController { public string Name { get; set; } public int Version { get; set; } public Type Type { get; set; } }
Il metodo SelectController sfrutta questa collection per determinare il controller più opportuno da restituire, dopo averne determinato nome e versione in base al routing corrente:
public override HttpControllerDescriptor SelectController( HttpRequestMessage request) { string controllerName = this.GetControllerName(request); if (string.IsNullOrEmpty(controllerName)) return base.SelectController(request); string versionValue = (string)request.GetRouteData().Values["version"]; int version = int.Parse(versionValue.Substring(1)); var controller = _versionedControllers.Value .Where(x => string.Equals( x.Name, controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)) .Where(x => x.Version <= version) .OrderByDescending(x => x.Version) .FirstOrDefault(); if (controller == null) return base.SelectController(request); return new HttpControllerDescriptor( _configuration, controllerName, controller.Type); }
In particolare, nella query LINQ, cerchiamo all'interno della collection un controller del nome richiesto, la cui versione sia la più alta possibile rispetto a quella richiesta: questo ci permette, per esempio, di far sì che se il client ha richiesto V2 di CustomerController, ma CustomerController è presente solo in versione V1, il selector effettui il fallback su quest'ultima invece che restituire un errore 404.
Si tratta di una tecnica molto comoda perchè ci permette di creare nuove versioni solo dei controller effettivamente modificati, senza necessariamente duplicare il tutto.
L'ultimo passaggio è configurare ASP.NET Web API per utilizzare il nostro selector. Possiamo farlo nel file WebApiConfig.cs con la riga di codice in basso.
config.Services.Replace( typeof(IHttpControllerSelector), new VersionedControllerSelector(config));
Vista la lunghezza dell'esempio, non abbiamo incluso nello script tutti i singoli passaggi. Chi fosse interessato, tuttavia, può dare un'occhiata al codice in allegato.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Usare le collection expression per inizializzare una lista di oggetti in C#
Inference di dati strutturati da testo con Semantic Kernel e ASP.NET Core Web API
Generare token per autenicarsi sulle API di GitHub
Migliorare l'organizzazione delle risorse con Azure Policy
Sostituire la GitHub Action di login su private registry
Usare una container image come runner di GitHub Actions
Supportare lo HierarchyID di Sql Server in Entity Framework 8
Gestire la cancellazione di una richiesta in streaming da Blazor
Aprire una finestra di dialogo per selezionare una directory in WPF e .NET 8
Eseguire le GitHub Actions offline
Sfruttare lo stream rendering per le pagine statiche di Blazor 8
Sfruttare al massimo i topic space di Event Grid MQTT
I più letti di oggi
- Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Utilizzare il metodo CountBy di LINQ per semplificare raggruppamenti e i conteggi
- Eseguire script pre e post esecuzione di un workflow di GitHub
- Creare una libreria CSS universale: Cards
- Migliorare l'organizzazione delle risorse con Azure Policy