Wprowadzenie

Podczas tworzenia nowego projektu Web API (nie mówimy tutaj o .NET Core) dodawana jest automatycznie paczka nuget pod nazwą Microsoft ASP.NET Web API Help Page. Paczka ta pozwala wygenerować zawartość strony pomocy dla utworzonego API. W przypadku świeżo utworzonego projektu (z mojej strony zmiana nazwy kontrolera) wygląda następująco: Strona pomocy z ASP.NET Dokumentacja w tej postaci jest dobra na sam początek pracy, brakuje tutaj jednak interakcji na żywo i wywoływania metod. Z pomocą przychodzi nam jednak Swagger.

Swagger jest prostą a jednocześnie niezywkle potężną reprezentacją Twojego RESTful API. Dzięki największemu ekosystemowi narzędzi API jest wspierana przez tysiące programistów na całym świecie oraz dostępna dla prawie każdego, nowoczesnego, języka programowania i środowiska wdrożeniowego. Dzięki interfejsowi obsługującemu Swagger otrzymujesz interaktywną dokumentację, generowanie SDK klienta oraz "odkrywczość" całej implementacji - swagger.io

Swagger: dokumentacja

Istotną informacją jest fakt, iż instalacja Swagger nie zastępuje domyślnych stron pomocy. Mogą być one używane równocześnie i nie powodują wzajemnych konfliktów.

W celu instalacji pakietu przechodzimy do Nuget Package Manager w poszukiwaniu poniższej paczki: Nuget Package Manager

Po zainstalowaniu przejdź do folderu App_Start - możesz tam zobaczyć nowy plik konfiguracyjny SwaggerConfig.cs. Swagger config W tym pliku włączamy domyślną obsługę oraz możemy dokonać niezbędnej konfiugracji. Warto mieć na uwadzę, że 90% kodu jest zakomentowane (z odpowiednimi informacjami do rozszerzenia konfiguracji). Włączone są jedynie podstawowe opcje.

Minimalna konfiguracja niezbędna do działa (odkomentowana) prezentuje się w następujący sposób:

GlobalConfiguration.Configuration
  .EnableSwagger(c => c.SingleApiVersion("v1", "Tytuł Twojej dokumentacji (W moim przpadku: SwaggerUI)"))
  .EnableSwaggerUi();

Jesteśmy już gotowi do uruchomienia naszego projektu. W tym celu należy dodać do naszego adresu końcówkę /swagger

Domyślny kontroler nieco zmodyfikowałem, po uruchomieniu dokumentacji generowanej przez Swagger przedstawia się w następujący sposób: Pierwsze uruchomienie

Z tego poziomu możemy wyświetlić szczegóły danej metody a z pomocą przycisku "Try it out!" możemy nawiązać połączenie z tym konkretnym API: Swagger: Try it out!

Swagger: komentarze XML

Podstawowa konfiugracja jest dobra na samym początku. W tej cześci skupimy się na większej personalizacji naszej dokumentacji. Możemy powiedzieć Swashbuckle, aby używał komentarzy XML, które pozwalą na dodanie znacznie większej liczby szczegółów do metadanych Swagger. Są to te same komentarze XML z których korzystają strony pomocy ASP.NET.

W pierwszym kroku należy włączyć tworzenie dokumentacji XML podczas kompilacji. Przechodzimy do właściwości naszego projektu a następnie, w zakładce Build, zaznaczamy XML documentation file: Włączenie dokumentacji XML Możemy śmiało zostawić domyślną ścieżkę pliku – będzie ona nam jednak potrzebna do konfiguracji.

Musimy jednak odpowiednio zaktualizować naszą konfiugrację. Swashbuckle musi dodać nasze komentarze XML do metadanych Swagger. Dodajemy poniższa linię do naszej konfiguracji, tj. pliku SwaggerConfig.cs (potrzebujemy również ścieżki do pliku z poprzedniego kroku):

c.IncludeXmlComments(string.Format(@"{0}\bin\SwaggerUI.XML", System.AppDomain.CurrentDomain.BaseDirectory));
Nasza docelowa konfiguracja przedstawia się następujący sposób:
GlobalConfiguration.Configuration
  .EnableSwagger(c =>
    {
      c.SingleApiVersion("v1", "Tytuł Twojej dokumentacji (W moim przpadku: SwaggerUI)");
      c.IncludeXmlComments(string.Format(@"{0}\bin\SwaggerUI.XML",           
                           System.AppDomain.CurrentDomain.BaseDirectory));
    })
  .EnableSwaggerUi();
Jeżeli nie mamy jeszcze żadnych komentarzy, warto poświecić na to kilka chwil i je dodać: Komenatrze XML dla klasy

Po ponowym uruchomieniu projektu powiniśmy zobaczyć zdecydowanie więcej szczegółów: Opis modelu z komentarzami XML

Swagger: opis wyliczeń za pomocą tekstu

Moja klasa CarModel zawiera typ wyliczeniowy, który reprezentuje typ silnika zamontowany w pojeździe: Rodzaj silnika Domyślnie Swagger wyświetla wartości wyliczeniowe jako ich reprezentację numeryczną. Nie jest to w żadnej sposób opisowe dlatego zmienimy to zachowanie. Dodajemy do naszej konfiugracji poniższy wpis:

c.DescribeAllEnumsAsStrings();
Możemy teraz zobaczyć, że wartości wyliczenia wyświetlane są jako ciąg znaków: Rodzaj silnika

To tylko kilka z wielu opcji konfiguracyjnych, które możesz określić w Swashbuckle, aby utworzyć/rozszerzyć metadane Swagger. Pełna lista opcji konfiguracyjnych znajduje się pod poniższym adresem https://github.com/domaindrivendev/Swashbuckle - zachęcam do zapoznania się z dokumentacją

Swagger: plik JSON

To co do tej pory zobaczyliście to reprezentacja UI naszych metadanych API Swagger. Aby zobaczyć co tak naprawdę kryję się pod pojęciem Swagger należy przejść do adresu URL znajdującego się w nagłówku dokumentacji:

					{
   "swagger":"2.0",
   "info":{
      "version":"v1",
      "title":"SwaggerUI"
   },
   "host":"localhost:58122",
   "schemes":[
      "http"
   ],
   "paths":{
      "/api/Cars":{
         "get":{
            "tags":[
               "Cars"
            ],
            "summary":"Metoda pozwalająca na pobranie listy samochodów",
            "operationId":"Cars_Get",
            "consumes":[
            ],
            "produces":[
               "application/json",
               "text/json",
               "application/xml",
               "text/xml"
            ],
            "responses":{
               "200":{
                  "description":"OK",
                  "schema":{
                     "type":"array",
                     "items":{
                        "$ref":"#/definitions/CarModel"
                     }
                  }
               }
            }
         },
         "post":{
            "tags":[
               "Cars"
            ],
            "operationId":"Cars_Post",
            "consumes":[
               "application/json",
               "text/json",
               "application/xml",
               "text/xml",
               "application/x-www-form-urlencoded"
            ],
            "produces":[
            ],
            "parameters":[
               {
                  "name":"value",
                  "in":"body",
                  "required":true,
                  "schema":{
                     "type":"string"
                  }
               }
            ],
            "responses":{
               "204":{
                  "description":"No Content"
               }
            }
         }
      },
      "/api/Cars/{id}":{
         "get":{
            "tags":[
               "Cars"
            ],
            "operationId":"Cars_Get",
            "consumes":[
            ],
            "produces":[
               "application/json",
               "text/json",
               "application/xml",
               "text/xml"
            ],
            "parameters":[
               {
                  "name":"id",
                  "in":"path",
                  "required":true,
                  "type":"integer",
                  "format":"int32"
               }
            ],
            "responses":{
               "200":{
                  "description":"OK",
                  "schema":{
                     "type":"string"
                  }
               }
            }
         },
         "put":{
            "tags":[
               "Cars"
            ],
            "operationId":"Cars_Put",
            "consumes":[
               "application/json",
               "text/json",
               "application/xml",
               "text/xml",
               "application/x-www-form-urlencoded"
            ],
            "produces":[
            ],
            "parameters":[
               {
                  "name":"id",
                  "in":"path",
                  "required":true,
                  "type":"integer",
                  "format":"int32"
               },
               {
                  "name":"value",
                  "in":"body",
                  "required":true,
                  "schema":{
                     "type":"string"
                  }
               }
            ],
            "responses":{
               "204":{
                  "description":"No Content"
               }
            }
         },
         "delete":{
            "tags":[
               "Cars"
            ],
            "operationId":"Cars_Delete",
            "consumes":[
            ],
            "produces":[
            ],
            "parameters":[
               {
                  "name":"id",
                  "in":"path",
                  "required":true,
                  "type":"integer",
                  "format":"int32"
               }
            ],
            "responses":{
               "204":{
                  "description":"No Content"
               }
            }
         }
      }
   },
   "definitions":{
      "CarModel":{
         "description":"Definicja pojazdu",
         "type":"object",
         "properties":{
            "Brand":{
               "description":"Marka samochodu",
               "type":"string"
            },
            "Model":{
               "description":"Model samochodu",
               "type":"string"
            },
            "Engine":{
               "description":"Pojemność silnika",
               "type":"string"
            },
            "Power":{
               "description":"Moc silnika",
               "type":"string"
            },
            "EngineType":{
               "description":"Rodzaj silnika",
               "enum":[
                  "Diesel",
                  "Petrol"
               ],
               "type":"string"
            }
         }
      }
   }
}

W ten sposób można "odkryć" i poznać cały interfejs API. Te metadane pozwalają poinformować inne API o możliwości wzajemnych oddziaływań (lub krócej: dostępnych akcji). Dzięki tej dokumentacji można również utworzyć bibliotekę klienta, którą następnie możemy przekazać do klientów/użytkowników/partnerów, w celu przejrzystego opisania interakcji z naszym API.