Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 28 de enero de 2020
Open API La OpenAPI Specification (OAS) es un estándar abierto para la descripción de APIS REST promovido por la Iniciativa OpenAPI, un consorcio de compañías de primer nivel como Microsoft, Google, IBM, Paypal y otros.

El objetivo de OpenAPI es conseguir una fórmula normalizada para describir las capacidades de un servicio REST, independientemente de los lenguajes o tecnologías con las que sea implementado. Esta descripción, normalmente especificada en formato JSON o YAML, permite a clientes (sean humanos o máquinas) descubrir servicios, comprender sus capacidades y conocer sus detalles de funcionamiento sin necesidad de tener acceso a su código fuente o documentación textual.

Esta especificación formal abre interesantes posibilidades, pues a partir de la definición estandarizada de servicios es posible, entre otros,
  • disponer de herramientas de diseño y modelado de servicios,
  • generar automáticamente páginas de documentación,
  • generar automáticamente código cliente y servidor para dichos servicios para distintos, frameworks y lenguajes de programación,
  • generar automáticamente código de testing y validaciones,
  • o generar automáticamente mocks de servicios.

¿Y qué tiene que ver Swagger en todo esto? Pues bastante, porque Swagger fue la semilla de OpenAPI. De hecho, Swagger era un proyecto liderado por la compañía Smartbear, que definió las dos primeras versiones de la especificación, y luego la donó a la nueva iniciativa abierta OpenAPI, quedando renombrada a partir de ese momento a "OpenAPI Specification". Por tanto, a efectos prácticos, cuando hablamos de la especificación Swagger y de OpenAPI, en muchas ocasiones nos estamos refiriendo a lo mismo.

A partir de la puesta en marcha de la iniciativa OpenAPI, el término Swagger pasó a utilizarse principalmente para denominar el framework o conjunto de herramientas que implementan la especificación, tanto de forma comercial como open source: diseñadores, generadores de código, parsers, generadores de documentación, etc.

La web Swagger.io es el sitio de referencia para las herramientas básicas y open source de Swagger.

Herramientas en Swagger.io

¿Qué pinta tiene la descripción OpenAPI de un servicio?

Imaginemos una API tan simple como la siguiente, creada con ASP.NET Core MVC:
[Route("[controller]")]
public class CalculatorController : ControllerBase
{
    /// <summary>
    /// Divides two numbers
    /// </summary>
    /// <param name="a">The dividend</param>
    /// <param name="b">The divisor</param>
    /// <response code="200">Result of dividing "a" by "b"</response>
    /// <response code="400">It's not possible to divide by zero</response>
    [HttpGet("[action]/{a}/{b}")]
    [Produces("application/json")]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public ActionResult<int> Divide(int a, int b)
    {
        if (b == 0)
        {
            return BadRequest();
        }
        return a / b;
    }
}
La descripción o esquema OpenAPI de este servicio en formato JSON podría ser la siguiente (he añadido algunos comentarios para explicar los principales puntos):
{
  "openapi": "3.0.1",
  "info": {
    "title": "Calculator",
    "version": "v1"
  },
  "paths": {
    "/Calculator/Divide/{a}/{b}": {        // <-- Ruta del endpoint
      "get": {                             // <-- Verbo HTTP
        "tags": [
          "Calculator"
        ],
        "summary": "Divides two numbers",
        "parameters": [
          {
            "name": "a",                   // <-- Parámetro "a"
            "in": "path",
            "description": "The dividend",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "b",                   // <-- Parámetro "b"
            "in": "path",
            "description": "The divisor",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {                         // <-- Retorno 200 OK
            "description": "The result of dividing \"a\" by \"b\"",
            "content": {
              "application/json": {
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          },
          "400": {                         // <-- Retorno 400 Bad request
            "description": "It's not possible to divide by zero"
          }
        }
      }
    }
  }
}
Sin duda algo verboso, pero no tenemos que preocuparnos por ello porque vamos a ver algo más adelante que un componente llamado Swashbuckle puede generar esta descripción por nosotros :)

Lo importante es que el contenido es bastante claro y que el servicio queda especificado por completo, al más mínimo detalle.

Describiendo nuestras APIs HTTP

El paquete NuGet Swashbuckle.AspNetCore proporciona componentes para generar de forma automática la descripción OpenAPI de servicios creados con ASP.NET Core. Obviamente tendremos que ayudarlo un poco, como habéis visto en el servicio anterior, por ejemplo introduciendo comentarios o indicando expresamente los tipos de resultado, porque de otra forma este componente no podría aportar tanta información a los clientes.

Pero la ventaja es que esto lo haremos a nivel de código, e incluso disponemos de analizadores estáticos de Open API que nos ayudarán a completar la información que necesitemos para lograr una documentación de calidad.

Para generar automáticamente la documentación de nuestros API, lo primero que debemos hacer es instalar el paquete Swashbuckle.AspNetCore y, tras ello, registrar sus servicios en el inyector de dependencias de ASP.NET Core:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("api", new OpenApiInfo { Title = "Awesomic calculator", Version = "v1" });
        var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
        var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
        c.IncludeXmlComments(xmlPath);
    });
    ...
}
En la llamada a SwaggerDoc() establecemos los parámetros esenciales: el nombre que vamos a dar al documento generado, y luego alguna metainformación básica sobre el API, como su nombre descriptivo o versión (ambos aparecerán en la descripción OpenAPI).

A continuación, indicamos al componente dónde puede encontrar el archivo XML con la documentación de clases y métodos, obteniendo primero la ruta correcta y pasándosela al método IncludeXmlComments().

Podemos activar la generación de documentación XML desde las propiedades del proyecto, o bien introduciendo el siguiente bloque en el archivo .csproj (fijaos que el <NoWarn> es sólo para que el sistema no nos avise en otras clases que tengamos sin documentar):
<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
  <NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Tras el registro de servicios, ya sólo tenemos que añadir al pipeline el middleware que se encargará de retornar la descripción del API:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseSwagger();
    ...
}
¡Y esto es todo! Usando la configuración por defecto, una petición a la ruta /swagger/api/swagger.json retornará el esquema de la API según la especificación OpenAPI. Fijaos que ese "api" de la ruta debe corresponder con el nombre que dimos al documento cuando registramos el servicio anteriormente:

Especificación OpenAPI de nuestro servicio

A partir de este momento,ya podremos comenzar a utilizar herramientas que se alimenten de esta definición como Swagger UI o generadores de código, pero, para no extendernos más, lo dejaremos para otro post más adelante :)

Publicado en Variable not found.

4 Comentarios:

Elena G dijo...

¿Y cómo podemos versionar APIs con Swagger en .NET Core?

¡Un saludo!

José María Aguilar dijo...

Hola, Elena!

Con Swagger no podrás versionar las APIs, sino describirlas :) Pero entiendo que te refieres a cómo describir una API versionada, ¿no?

En este caso, al final dependerá del mecanismo de versionado que hayas elegido, pero básicamente consiste en crear tantas definiciones Swagger como versiones tengas. Puedes ver algunos ejemplos de uso con ApiVersioning en este issue de Github: https://github.com/Microsoft/aspnet-api-versioning/issues/271

Saludos!

Alfonso dijo...

Hola,

Me encuentro con una duda, en el ejemplo que das y en los que he visto, los valores de entrada siempre son definidos, Me refiero a que son String o Int etc.

pero en mi caso el json de entrada es recivido como un Envelop como el siguiente ejemplo

Ej

public async Task GetObtenerHistorialLm([FromBody] EnvelopedObject.Enveloped InObj)

try
{
string inDate = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK");

if (InObj.body.pasaporte == null || InObj.body.pais == null)


await _Service.GetObtenerHistorialLmAsync((int)InObj.body.pasaporte , (string)InObj.body.pais , inDate);


como puedo hacer para que mi swagger vea estos valores y me los pinte en el xml


No se, si logro explicar mi duda, ya que en el xml tengo que indicarle los tipos de parámetros que recibo en el controller.

XML




José María Aguilar dijo...

Hola!

Pues la verdad es que no te entendí demasiado bien, creo que me falta algo de información de contexto sobre esos tipos de datos que utilizas.

En general, swagger describirá todos los tipos de datos, incluido tipos custom. Sin embargo, si como parecen indicar tus casts tienes muchos tipos no específicos (object o similares), quizás podrías personalizar el esquema. Si crees que va por aquí la cosa, puedes leer algo sobre ello en la documentación oficial.

Saludos!