Gestire il versioning dei servizi con ASP.NET Web API

di Marco De Sanctis, in ASP.NET Web API,

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

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi