En este post veremos como integrar la funcionalidad que proporciona “Swagger API” mediante un interfaz de usuario “Swagger UI” en un proyecto basado en Asp.NET core WebApi. La versión de .NET core utilizada es la 2.1.2.

Swagger, para quién  no lo conozca, es un framework que permite acceder a un API Rest a través de un interfaz de usuario y que también proporciona soporte para crear un interfaz que permita realizar pruebas con el API. Además de esto Swagger permite hacer muchas otras cosas. Mi recomendación es revisar su documentación online:

Para integrar parte de la funcionalidad de Swagger en nuestro proyecto haremos uso de la librería SwashBuckle:

Este librería se puede instalar con Nuget y como hay 2 versiones hay que asegurarse de instalar la versión para AspNet.Core:

image

Una vez instalada, nos proporciona entre otros 2 componentes esenciales:

  1. SwaggerGen: Que, mediante reflexión lee la estructura lógica de nuestro servicio: “Routes”, “Models”, “Actions” y “Controllers” y  expone un objeto JSON que describe esta estructura de acuerdo a las especificaciones de Swagger 2.0.
  2. SwaggerUI: Que interpreta el objeto JSON generado y construye una interfaz de usuario que permite acceder de forma interactiva a la documentación y también realizar pruebas con el nuestro API.

En primer lugar añadimos, en la clase “Startup”, “SwaggerGen” como un servicio a nuestro proyecto (vamos a hacerlo primero con una configuración básica y después lo iremos ampliando con una más avanzada) :

//Swagger setup. Need to be added after MVC setup.
services.AddSwaggerGen(cfg => {
    cfg.SwaggerDoc(
        "bioregProjectsApi",
        new Info
        {
            Version = "0.6",
            Title = "API for projects administration"
        });
});

después configuramos nuestra aplicación para que haga uso de “Swagger UI”, que agregará a nuestra aplicación los elementos necesarios para generar la interfaz de usuario:

app.UseSwaggerUI(cfg =>
{
    cfg.SwaggerEndpoint("/swagger/bioregProjectsApi/swagger.json", "Bioreg Projects Api");
});

y ya podemos probar nuestra Api RestFul con la interfaz Swagger UI integrada:

                 image

Si desplegamos una de las acciones podemos ver la documentación con los parámetros y los modelos utilizados y un botón “TryOut”  para probar la operación.

Por defecto “SwaggerGen” nos ha creado también un URI: “/swagger/[apiname]/swagger.json” que expone el objeto JSON que describe nuestra API y que podemos usar en el Hub de Swagger. Eso sí, hay que tener en cuenta que el código JSON generado por “SwaggerGen” sigue la especificación “Swagger 2.0” y no la “OpenApi 3.0”.

Bién, ya tenemos funcionando nuestra interfaz “Swagger UI”, podemos ahora realizar algunos ajustes a la configuración para conseguir un interfaz  más rica e intuitiva. Y en lugar de presentar un conjunto de ajustes en bloque, voy a ir presentándolos paso a paso y presentando el código de la misma forma:

Ruta por defecto

Por defecto, el “Swagger UI” generado es accesible desde el URI “/swagger”, pero podemos configurarlo para que se acceda desde el URI que queramos o incluso especificar que esté vacío y que se acceda directamente desde la URL como en el siguiente ejemplo:

app.UseSwaggerUI(cfg =>
{
    cfg.SwaggerEndpoint("/swagger/bioregProjectsApi/swagger.json", "Bioreg Projects Api");
    cfg.RoutePrefix = string.Empty;
});

ahora el acceso a “Swagger UI” será: http://myapi/  en lugar de http://myapi/swagger.

Personalizar usando comentarios XML

SwashBuckle permite también utilizar los comentarios XML especiales de C#, para personalizar y enriquecer la información proporcionada por la interfaz “Swagger UI”.

Para ello en primer lugar tenemos que habilitar la generación de documentación XML en las propiedades de nuestro proyecto:

image

después agregamos la siguiente línea a la configuración de “SwaggerGen” para indicarle que se incluyan los comentarios XML en la generación del interfaz de usuario:

services.AddSwaggerGen(cfg => {
    cfg.SwaggerDoc(
        "bioregProjectsApi",
        new Info
        {
            Version = "0.6",
            Title = "API for projects administration"
        });
    cfg.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Edigital.Bioreguladores.Projects.Api.xml"));
    cfg.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Edigital.Bioreguladores.Projects.Data.Domain.xml"));
});

en el ejemplo de anterior he agregado también el fichero con la documentación generada para el ensamblado que contiene el dominio de datos. Estos comentarios sobre las clases del dominio de datos se utilizarán para enriquecer la información que presenta el “Swagger UI” de los modelos de datos utilizados en el API. Por ejemplo:

/// <summary>
/// The email or the username of the user owner of the project. Both need to be unique.
/// </summary>
/// <example>plutoh@gmail.com</example>
[BsonElement("OwnerId"), BsonRequired]
public string Owner { get; set; }

se mostraría de la siguiente forma siempre que aparezca el modelo en la documentación:

image

Una vez agregados los ficheros a la configuración de “SwaggerGen”, podemos usar los tags más comunes (summary, param, remarks, return, etc…) de comentarios XML para enriquecer la documentación de nuestra api.

El tag “response” no es un tag oficial pero está soportado por «SwaggerGen». Este tag nos permite añadir documentación sobre las respuestas que produce nuestro API. Si necesitamos especificar un tipo concreto de objeto para algúna de las respuestas, tenemos que agregar un atributo “ProducesResponseType”, indicando el tipo, al método además del comentario XML tal y como aparece en el ejemplo:

/// <summary>
/// Get a set of projects by a query.
/// </summary>
/// <returns>A collection of liteweight version of projects.</returns>
/// <param name="pageSize"></param>
/// <param name="page">Is Optional, will be 0 if its not provided.</param>
/// <param name="query">
/// </param>
/// <remarks>Page parameter is optional and is set to 1 if its not provided</remarks>
/// /// <response code="200">Success, return a collection of projectLiteDto. Or an empty array if the the query does not find any projects.</response>
/// <response code="400">If query has a bad format or has unknown fields.</response> 
/// <response code="501">Something wrong occurs with this operation.</response>
[HttpPost]
[ProducesResponseType(typeof(IEnumerable<ProjectLiteDto>),200)]
[ApiExplorerSettings(GroupName = "bioregProjectsApi")]
public IEnumerable<ProjectLiteDto> Get([FromBody] string query, [FromQuery] int pageSize = 20, int? page = 0)
{
    return _projectRepository.GetProjects(query, pageSize, page);
}

Y en la siguiente imagen, podemos ver donde aparece la información de cada uno de los tags.

image

Establecer la vista del modelo por defecto

Es otra característica interesante de configurar. Por defecto “Swashbuckle” nos muestra en el “Swagger UI” una representación plana en formato JSON del modelo. Podemos cambiar este comportamiento, para que nos muestre en su lugar una representación gráfica del modelo, usando el método DefaultModelRendering con la opción ModelRendering.Model:

app.UseSwaggerUI(cfg =>
{
    cfg.SwaggerEndpoint("/swagger/bioregProjectsApi/swagger.json", "Bioreg Projects Api");
    cfg.SwaggerEndpoint("/swagger/bioregProjectsAsyncApi/swagger.json", "Bioreg Projects Async Api");
    cfg.RoutePrefix = string.Empty;
    cfg.DefaultModelRendering(ModelRendering.Model);
});

image

Agrupar operaciones por tipo de método Http

Otra de las cosas interesantes que podemos configurar es el orden y la agrupación de las operaciones que aparecen en el “Swagger UI”. En este ejemplo vamos a agrupar las operaciones por el tipo de método Http. En un API Restful, lo relevante es el método Http utilizado para realizar la llamada. En nuestro ejemplo, que se trata de un API Restful lo configuraremos así, pero en el caso de un API que NO sea Restful podría ser interesante agrupar las operaciones de otra forma.

Para realizar esta configuración utilizaremos el método «TagActions» de la configuración de “SwaggerGen”:

services.AddSwaggerGen(cfg => {
    cfg.SwaggerDoc(
        "bioregProjectsApi",
        new Info
        {
            Version = Configuration["Prefs:ApiVersion"],
            Title = Configuration["Prefs:ApiTitle"]
        }
    );
    .
    .
    .
    cfg.TagActionsBy(p => p.HttpMethod); //Group and order by httpMethod.
});

lo cual forzará a que el «Swagger UI» generado muestre las operaciones agrupadas por tipo de método Http.

image

Es posible agrupar de otras formas, por ejemplo con otros tags. Esta característica, aún siendo útil, se sale un poco de la extensión de este artículo. De momento para APIs de pequeño tamaño esta agrupación es más que suficiente.

Agrupar operaciones por versión

Es posible otro tipo de agrupación más general a modo de versión, aunque la aplicación practica de este tipo de agrupación depende de cada proyecto. En este modo de agrupación es posible establecer que a cada versión le corresponda un fichero JSON de metadatos diferente.

En primer lugar tenemos que configurar un segundo “SwaggerDoc” para la otra versión. En el  ejemplo he configurado 2 versiones: una para las operaciones síncronas y otra para las operaciones asíncronas:

//Swagger setup. Need to be added after MVC setup.
services.AddSwaggerGen(cfg => {
    cfg.SwaggerDoc(
        "bioregUsersApi",
        new Info
        {
            Version = Configuration["Prefs:ApiVersion"],
            Title = Configuration["Prefs:ApiTitle"]
        });
    cfg.SwaggerDoc(
        "bioregUsersApiAsync",
        new Info {
            Version = Configuration["Prefs:AsyncApiVersion"],
            Title = Configuration["Prefs:AsyncApiTitle"]
        });
    cfg.ExampleFilters();
    cfg.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Edigital.Bioreguladores.Users.Api.xml"));
    cfg.TagActionsBy(p => p.HttpMethod); //Group and order by httpMethod.
    cfg.DescribeAllEnumsAsStrings(); //show enums names instead enum values.
});

después, tenemos que agregar un segundo Endpoint para el acceso a nuestra versión adicional:

app.UseSwaggerUI(cfg => 
{
    cfg.SwaggerEndpoint("/swagger/bioregUsersApi/swagger.json", "Bioreg User Api");
    cfg.SwaggerEndpoint("/swagger/bioregUsersApiAsync/swagger.json", "Bioreg Users Async Api");
    cfg.RoutePrefix = string.Empty;
    cfg.DefaultModelRendering(ModelRendering.Example);
});

y por último, haciendo uso del «ApiExplorer«, que ya se incluye en la versión 2.1 de AspNet.Core, decoramos las operaciones con un atributo «ApiExplorerSettings» para agruparlas conforme a la versión:

[HttpDelete("{projectId}")]
[ApiExplorerSettings(GroupName = "bioregProjectsApi")]
public void DeleteById(string projectId) => _projectRepository.RemoveProject(projectId);

[HttpDelete("{projectId}")]
[ApiExplorerSettings(GroupName = "bioregProjectsAsyncApi")]
public async Task DeleteByIdAsync(string projectId) => await _projectRepository.RemoveProjectAsync(projectId);

y ya podemos ver que en el “Swagger UI” generado aparece en la parte superior derecha un desplegable con los accesos a las versiones configuradas:

image

y aquí termina esta breve introducción a la integración de “Swagger UI” usando SwashBuckle.

Swashbuckle es un fantástico framework, que ofrece unas amplias posibilidades de extensión y personalización, aunque aquí sólo hemos repasado algunas de ellas. Yo creo que algunas de las más interesantes.

Espero que este artículo sirva como punto de partida de las posibilidades que ofrece la integración de un interfaz de usuario en nuestras APIs Rest.