Swagger Programiści codziennie tworzą jakąś aplikację sieciową typu REST. Teraz nastaje pytanie, jak najlepiej zrozumieć jak dane API działa. Do tego mamy dokumentacje, ale jeśli pracujesz w szybkich, zamkniętych projektach to takiej dokumentacji może nie być.

Potem ktoś dziedziczy projekt i nie wie na co on patrzy.

Wypadałoby znać wszystkie metody, klasy typy zwracane, argumenty w danym API. Dołączyć do tego możliwość szybkiego uruchomienia danej metody z przykładami.

Swagger i Open API

Swagger to specyfikacja służąca do opisywania działania API REST-owych. Swagger też często jest nazywany jako Open API.  Ma on pozwalać ci zrozumieć co potrafi dana usługa.

Pomysł jest bardzo dobry. Nawet nie wiesz ile razy w karierze musiałem sprawdzać działanie aplikacji REST korzystając z jakichś narzędzi jak  : PostMan.

Specyfikacja Swagger jest tak naprawdę plikiem swagger.json i jest on generowany przez odpowiednie biblioteki. Opisuje on zdolności danej usługi.

Swagger UI nazywamy interfejs graficzny, który pozwala na te wszystkie informacji wyświetlić w przyjazny sposób. 

Domyślnie dl .NET znajduje się on w paczce NuGet : Swashbuckle. Dodatkowo ta paczka jest domyślnie dodawana do szablonu ASP.NET CORE Web API dla .NET 5.

Swagger UI w ASP.NET CORE 5

Trzeba tylko zaznaczyć opcję : Enable OpenAPI support.

Skoro Microsoft dodał ten framework do swoich domyślnych szablonów to rozumiesz, że narzędzie tutaj omawiane musi być bardzo dobre.

Integracja Swagger UI z twoją aplikacją

Jeśli z jakiego powodu nie korzystasz z szablonów możesz zadać sobie pytanie, jak to wszystko dodać do swojego projektu.

Wszystko jest tak jak mówiłem wcześniej w paczkce : Swashbuckle.AspNetCore

A ona zawiera :

  • Swashbuckle.AspNetCore.Swagger : Zawiera on middleware dla ASP.NET CORE, który wystawi obiekt SwaggerDocument jako plik JSON.
  • Swashbuckle.AspNetCore.SwaggerGen : Generator obiektu SwaggerDocument, który utworzy się podstawie twoich kontrolerów i metod
  • Swashbuckle.AspNetCore.SwaggerUI: Wersja narzędzia Swagger UI. Pobierze on plik JSON i wyświetli informacje o twojej usłudze jako piękną stronę internetową.

Instalujesz więc paczkę Swashbuckle.AspNetCore

Install-Package Swashbuckle.AspNetCore 

W klasie startup.cs dodajesz generator Swagger do kolekcji usług.

public void ConfigureServices(IServiceCollection services)
{
    // Możesz dodać więcej niż jeden Swagger dokument
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "AddingStuffAndCheckingI", Version = "v1" });                
    });
    services.AddControllers();
}

W metodzie Configure określasz, pod jaki adresem ma być twój dokument JSON oraz strona internetowa z Swagger UI.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // middleware który doda Swagger JSON
        app.UseSwagger();
        // middleware do swagger-ui (HTML, JS, CSS, etc.),
        // tutaj określasz endpoint dla Swagger JSON
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "AddingStuffAndChecking v1");
        });
}

Co dalej:

Dodajmy jakąś klasę, aby zrobić szybkie demo.

public class Student
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Będziemy także potrzebowali jakiegoś kontrolera. 

[Route("api/[controller]")]
[ApiController]
public class StudnetController : Controller
{
    // GET: api/Student
    [HttpGet]
    public IEnumerable<Student> Get()
    {
        return GetStudents();
    }

    // GET: api/Student/5
    [HttpGet("{id}", Name = "Get")]
    public Student Get(int id)
    {
        return GetStudents().Find(e => e.Id == id);
    }

    // POST: api/Student
    [HttpPost]
    [Produces("application/json")]
    public Student Post([FromBody] Student Student)
    {
        return new Student();
    }

    // PUT: api/Student/5
    [HttpPut("{id}")]
    public void Put(int id, [FromBody] Student Student)
    {
    }

    // DELETE: api/Student/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {
    }

    private List<Student> GetStudents()
    {
        return new List<Student>()
        {
            new Student()
            {
                Id = 1,
                FirstName= "Jax",
                LastName = "Brighth",
            },
            new Student()
            {
                Id = 2,
                FirstName= "Johny",
                LastName = "Cage",
            }
        };
    }
}

W projekcie miałem inne kontrolery z innych wpisów na temat fajnych rzeczy w ASP.NET CORE. Dla ułatwienie za komentowałem te kontrolery.

moje kontrolery

Uruchommy teraz naszą aplikacje.

Pod adresem "https://localhost:44384/swagger/v1/swagger.json." mogę sobie zobaczyć następujący plik JSON. Pamiętaj, że port u Ciebie będzie innym.

{
  "openapi": "3.0.1",
  "info": {
    "title": "AddingStuffAndChecking",
    "version": "v1"
  },
  "paths": {
    "/api/Studnet": {
      "get": {
        "tags": [
          "Studnet"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Student"
                  }
                }
              },
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Student"
                  }
                }
              },
              "text/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Student"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Studnet"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Student"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/Student"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/Student"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Student"
                }
              }
            }
          }
        }
      }
    },
    "/api/Studnet/{id}": {
      "get": {
        "tags": [
          "Studnet"
        ],
        "operationId": "Get",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/Student"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Student"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/Student"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Studnet"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Student"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/Student"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/Student"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      },
      "delete": {
        "tags": [
          "Studnet"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Student": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    }
  }
}

Pod adresem "https://localhost:44384/swagger/index.html" znajdziesz Swagger UI. Pamiętaj, że port u Ciebie będzie inny.

Swagger UI jak wygląda

W każdej chwili możesz przejrzeć typy danych, na jakich twoja usługa operuje.

Tak nawet nie wiesz, jak ważną informacją może być czy coś może być NULL czy nie.

Klasa student w swagger ui

Swagger daje Ci możliwość wypróbowania każdej metody pod klawiszem "Try it out"

testowanie metody w Swagger UI

Kto by pomyślał, że to może być aż takie proste. Chociaż domyślam się, że testowanie tak metod gdzie istnieją warstwy autoryzacji może być utrudnione to nie powiem robi to wrażenie.

Jak możesz to rozszerzyć?

Możliwości tutaj się nie kończą. Po pierwsze możesz do opisu dokumentacji dodać informację o jego twórcy oraz licencje.

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1",
        new OpenApiInfo
        {
            Title = "AddingStuffAndChecking",
            Version = "v1",
            Description = "An API to get students",
            TermsOfService = new Uri("https://mojafirmalichydziwg.com/terms"),
            Contact = new OpenApiContact
            {
                Name = "Cezary Walenciuk",
                Email = "cezary222@gmail.com",
                Url = new Uri("https://twitter.com/walenciukc"),
            },
            License = new OpenApiLicense
            {
                Name = "Employee API ",
                Url = new Uri("https://mojafirmalichydziwg.com/license"),
            }
        });
});

Zostanie ona dodana tak do strony.

Dodanie więcej informacji w Swagger UI

Co, jeśli chciałbyś dodać jakieś swoje komentarze na temat danej metody.

Chciałbyś, aby te komentarze było widać w Swagger UI. Nie ma problemu, ale najpierw w opcjach projektu musi włączyć dokumentacje XML.

Dokumentacja XML w projekcie

Ten XML będzie załączony do Swagger. Trzeba mu tylko o tym powiedzieć.

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1",
        new OpenApiInfo
        {
            Title = "AddingStuffAndChecking",
            Version = "v1",
            Description = "An API to get students",
            TermsOfService = new Uri("https://mojafirmalichydziwg.com/terms"),
            Contact = new OpenApiContact
            {
                Name = "Cezary Walenciuk",
                Email = "cezary222@gmail.com",
                Url = new Uri("https://twitter.com/walenciukc"),
            },
            License = new OpenApiLicense
            {
                Name = "AddingStuffAndChecking API ",
Url = new Uri("https://mojafirmalichydziwg.com/license"), } }); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); });

Możesz na przykład dodać tylko opis do metody.

/// <summary>
/// Pobiera listę studentów
/// </summary>
/// <returns>Zwraca liste studentów</returns>
// GET: api/Student
[HttpGet]
public IEnumerable<Student> Get()
{
    return GetStudents();
}

Możesz też dodać cały opis użycia danej metody oraz jakie odpowiedzi HTTP może zwrócić. Ciekawe, że Swagger UI potrafi zauważyć, że coś innego jest zwracane dla inne kodu HTTP.

/// <summary>
/// Dodaje studenta
/// </summary>
/// <remarks>
/// Sample request:
///
///     POST api/Student
///     {
///       "firstName": "Jacek",
///       "lastName": "Stefaniuk",
///       "emailId": "Jacek.Stefaniuk@gmail.com"
///     }
/// </remarks>
/// response code="201">Zwraca nowego studenta/response>
/// <response code="400">Jeśli był przesłany NULL</response>
/// <response code="500">Coś poszło nie tak</response>
// POST: api/Student
[HttpPost]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
[Produces("application/json")]
public Student Post([FromBody] Student Student)
{
    if (Student == null)
    {
        this.HttpContext.Response.StatusCode = 400;
return null; } return new Student(); }

Oto jak to widać w dokumentacji.

jak to działa w Swagger UI. Mamy tutaj 200,400,500

Jeśli chodzi o same klasy, które określają nasze parametry to zawsze możesz skorzystać z atrybutów. Powiedzieć, że dane pole jest wymagane, a dany napis ma limit swojej długości.

public class Student
{
    [Required]
    public int Id { get; set; }

    [StringLength(255)]
    public string FirstName { get; set; }
    [StringLength(255)]
    public string LastName { get; set; }
}

W Swagger to też będzie można zobaczyć. Jak widzisz gwiazdka przy "Id" określa, że pole jest wymagane.

Klasa Student opisana w Swagger UI

Nie podoba Ci się domyślny wygląd Swagger. Zawsze możesz dołączyć swój plik CSS do niego.

Dodaj app.UseStaticFiles(), ponieważ domyślny szablon nie ma tej konfiguracji.

app.UseStaticFiles();
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI
        (c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json",
                "AddingStuffAndChecking v1");
                c.InjectStylesheet("/Swagger/Ui/StyleSheet.css");
            }

        );
}

W UseSwaggerUI skorzystajmy z metody "InjectStyleSheet". Plik CSS oczywiście musi się tam znajdować.

Dodanie StyleSheet

Zmienie pewne style, abyśmy mieli dowód, że to działa.

.swagger-ui .topbar {
    background-color: #444444;
    border-bottom: 3px solid red;
}

    .swagger-ui .topbar .download-url-wrapper .select-label {
        color: greenyellow !important;
    }

        .swagger-ui .topbar .download-url-wrapper .select-label select {
            border: 2px solid red;
        }

Jak widzisz obramowanie jest teraz czerwone w dwóch miejscach. Zmieniłem też tło paska oraz zmieniłem kolor napisu "Select a definition".

Przykład InjectCSS w Swagger UI

Zamiast wstrzykiwania CSS zawsze możesz pobrać cały jego projekt z GitHuba : https://github.com/swagger-api/swagger-ui/tree/master/dist 

Wtedy będziesz miał pełną kontrolę nad wyglądem takie dokumentacji.

To wszystko, jeśli chodzi o Swagger UI. Miłego tworzenia dokumentacji.