Swagger con ASP .NET Core (Versionado)

Hello world,

Continuando con el tema de la configuración de Swagger, en este post explicaré como configurar el tema del versionado.

Versionado

Como explique en un post pasado, van a existir varias maneras de poder versionar nuestras apis, si quereis leer el post, podéis hacerlo desde aquí. En este caso vamos a explicar como hacerlo de las siguientes maneras.

  • Incrustado en la url
  • Por querystring
  • Por cabecera
  • Por MediaType

Lo primero que tendremos que hacer para decirle a swagger mediante su generador de documentos es que vamos a tener varias versiones. Para ello simplemente tendremos que declarar tantos SwaggerDoc como versiones tengamos:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new Info { Title = "SPORTING API", Version = "v1" });
    c.SwaggerDoc("v2", new Info { Title = "SPORTING API", Version = "v2" });
});

Una vez definidas las versiones para nuestro generador, tendremos que configurar la siguiente convención invocando DocInclusionPredicate para que se asignen las distintas acciones de nuestros controladores, a los documentos (json) que se generarán. Para ello haremos lo siguiente:

c.DocInclusionPredicate((docName, apiDesc) => {
    var actionApiVersionModel = (ApiVersionModel)apiDesc.ActionDescriptor?.Properties
        .FirstOrDefault(w => ((Type)w.Key).Equals(typeof(ApiVersionModel))).Value;
    if (actionApiVersionModel == null)
    {
        return true;
    }
    return actionApiVersionModel.DeclaredApiVersions.Any()
        ? actionApiVersionModel.DeclaredApiVersions.Any(version => $"v{version.ToString()}".Equals(docName))
        : actionApiVersionModel.ImplementedApiVersions.Any(version => $"v{version.ToString()}".Equals(docName));
});

También tendremos que ir donde definimos la UI de swagger y añadiremos la versión/es para que se nos muestre por pantalla, como podemos ver más abajo, también le definimos el json que se generará en el punto anterior:

app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "SPORTING API V1");
    c.SwaggerEndpoint("/swagger/v2/swagger.json", "SPORTING API V2");
});
Sin títddddulo.png
Combo con las versiones

Con estos simples paso, ya tendríamos configurado nuestro swagger para que acepte el versionado. Ahora toca empezar a jugar con las opciones que nos ofrecen los distintos tipos de versionado y como poder configurar swagger para que sea lo más automático posible.


Incrustado en la url

Vamos a empezar a jugar con el versionado incrustado en la url, para ello anotaremos nuestro controlador de la siguiente manera:

[Route("api/v{version:apiVersion}/[controller]")]

Si ahora lanzamos la aplicación, veremos que nuestro swagger ya detecta que hay un parámetro en la url, y nos ofrece en la UI un campo para poder rellenarlo.

Capturddda.PNG
Captura swagger donde se ve el campo versión a rellenar

Esto esta bien, pero nos implicará tener que informarlo cada vez que quisiéramos ejecutar cada acción. Lo suyo sería que se informase automáticamente y nos despreocupamos de eso. Esto lo podremos hacer de una forma sencilla utilizando los distintos filtros que nos aporta swagger, donde podremos toquetear un poquito las tripas. El primer paso será poner nuestra versión en la url. Para ello crearemos un filtro que implementará la interfaz propia de swagger de IDocumentFilter y allí meteremos nuestra lógica:

public class SetVersionInPathFilter : IDocumentFilter
        {
            public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
            {
                //Nos recorremeros las ditintas URL que nos llegan en el documento de swagger y reemplazaremos
                //la versión que nos llega en la información del documento.
                swaggerDoc.Paths = swaggerDoc.Paths
                    .ToDictionary(
                        path => path.Key.Replace("{version}", swaggerDoc.Info.Version),
                        path => path.Value
                    );
            }
        }

Invocaremos este filtro desde la generación del documento y así las urls ya aparecerán con la versión incrustada, pero quedaría realizar otra acción, como vimos en la imagen anterior, en la UI nos solicitaba como parámetro el numero de versión, para evitar esto, lo que tendremos que hacer es jugar con otro de los filtros, en este caso con el filtro de operaciones, nos crearemos uno que implemente la interfaz IOperationFilter. Aquí lo que haremos será borrar los parámetros referentes a la versión que nos vienen.

public class RemoveVersionParameterFilter : IOperationFilter
    {
        public void Apply(Operation operation, OperationFilterContext operationFilterContext)
        {
            var parameterVersion = operation?.Parameters?.SingleOrDefault(param => param.Name.Equals("version"));
            operation?.Parameters?.Remove(parameterVersion);
        }
    }

Y por último ya solo nos quedaría utilizar estos filtros desde el generador de swagger:

c.OperationFilter<RemoveVersionParameterFilter>();
c.DocumentFilter<SetVersionInPathFilter>();

Si ahora volvemos a ejecutar nuestra aplicación, veremos que ya nos aparece la url con la versión incrustada, y nos ha desaparecido el campo donde informar la versión:

Capteeeura
Captura donde se ve la versión incrustada en la url

Por MediaType

Para versionar por MediaType, tendremos que jugar con el filtro de operaciones. Desde ahí podremos modificar la versión e incluirlo en los MediaTypes.

public class ApiVersionOperationFilter : IOperationFilter
    {
        public void Apply(Operation operation, OperationFilterContext operationFilterContext)
        {
            var actionApiVersionModel = operationFilterContext?.ApiDescription?.ActionDescriptor?.GetApiVersion();
            if (actionApiVersionModel == null)
            {
                return;
            }

            if (actionApiVersionModel.DeclaredApiVersions.Any())
            {
                operation.Produces = operation.Produces
                  .SelectMany(declaredApiVersions => actionApiVersionModel.DeclaredApiVersions
                    .Select(version => $"{declaredApiVersions};v={version.ToString()}")).ToList();
            }
            else
            {
                operation.Produces = operation.Produces
                  .SelectMany(implementedApiVersions => actionApiVersionModel.ImplementedApiVersions.OrderByDescending(o => o)
                    .Select(version => $"{implementedApiVersions};v={version.ToString()}")).ToList();
            }
        }
    }

y al igual que en el anterior, tendremos instanciarlo desde el generador:

c.OperationFilter<ApiVersionOperationFilter>();
Captuddddra
Captura swagger donde se ve el mediatype

Por querystring

En este caso tendremos que volver a jugar con el filtro del documento, y lo que haremos será recorrernos las distintas urls y concatenarles la versión:

public class SetVersionInPathFilter : IDocumentFilter
    {
        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext documentFilterContext)
        {
            swaggerDoc.Paths = swaggerDoc.Paths
                .ToDictionary(
                    path => path.Key + $"?api-version={swaggerDoc.Info.Version}",
                    path => path.Value
             );
        }
    }

Lo instanciaremos desde el DocumentFilter.

c.DocumentFilter<SetVersionInPathFilter>();

Y este será el resultado:

url
Captura donde se ve la versión como parametro de querystring

Por cabecera

Para añadirlo por cabecera también jugaremos con los filtros, esta vez con el DocumentFilter, para ello lo que haremos es para cada uno de los paths que nos llegan, le añadiremos una cabecera, asignándole el valor por defecto:

public class AddVersionHeader : IDocumentFilter
    {
        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
        {
            var version = swaggerDoc.Info.Version;

            foreach (var pathItem in swaggerDoc.Paths.Values)
            {
                TryAddVersionParamTo(pathItem.Get, version);
                TryAddVersionParamTo(pathItem.Post, version);
                TryAddVersionParamTo(pathItem.Put, version);
                TryAddVersionParamTo(pathItem.Delete, version);
            }
        }

        private void TryAddVersionParamTo(Operation operation, string version)
        {
            if (operation == null) return;

            if (operation.Parameters == null)
                operation.Parameters = new List();

            operation.Parameters.Add(new NonBodyParameter
            {
                Name = "api-version",
                In = "header",
                Type = "string",
                Default = version,
            });
        }
    }

Posteriormente lo instanciaremos desde el DocumentFilter.

c.DocumentFilter<AddVersionHeader>();
header.PNG
Swagger con el header

Y ya lo tendremos funcionando. Si quisiéramos que no se viera el parámetro, tendríamos que jugar con el css para ocultarlo. Para ello podríamos inyectarle al swagger UI un css.

*Este punto también es interesante, porque no solo nos sirve para meter en la cabecera de nuestras peticiones el versionado, ya que podríamos incluir cualquier parámetro que necesitásemos, ya sea en la cabecera, en el body


Pues hasta aquí el tema de el versionado con swagger, espero que os haya parecido interesante y sobre todo útil. Os dejo un enlace a cada uno de los ejemplos para que podáis descargarlo y debugarlo vosotros mismos!

Hasta pronto!

Anuncios
Swagger con ASP .NET Core (Versionado)

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s