Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Document and Test API with Swagger UI

0.00/5 (No votes)
17 Dec 2018 1  
More than often, developers test API, either through a browser request or using some clients such as POSTMAN, Advanced Rest Client (ARC).

Introduction

More than often, developers test API, either through a browser request or using some clients such as POSTMAN, Advanced Rest Client (ARC). To expose the functionality of the API, we also tend to expose the methods and descriptions, and associated data structure through some means which require additional work. To complement either or both of these functionalities, Swagger becomes handy which provides API documentation and API testing by configuration.

Swagger is a set of open-source tools built around the OpenAPI specification that can be used to auto-generation documentation for API. Swagger UI is a tool that can be used across API lifecycle. Swagger provides easy to navigate documentation and/or visualization of API resources and enables interaction with API possible from within the application itself making the development and testing effort, as well as end-user experience seamlessly smooth. In this article, I am going to discuss how to implement swagger in API and exemplify some use cases of Swagger UI. I will be using .NET Core 2.0 Web API application and using Visual Studio 2017 IDE. I have created a sample API application with a single controller and four methods as part of the demo which is available for download.

Swagger offers the most powerful and easiest to use tools to take full advantage of the OpenAPI Specification.

Configuration

Wiring-up Swagger on an application is fairly minimal and can be accomplished in four easy steps, namely - installation, import, registration, and endpoint enablement.

The package can be installed in the Package Manager Console, or alternatively by going to the NuGet Package Manager menu.

Install-Package Swashbuckle.AspNetCore

Figure 1: Installing Swagger in the Package Manager Console.

Once installed, Swagger must be imported into Startup.cs.

using Swashbuckle.AspNetCore.Swagger;

Then the Swagger as service must be registered within ConfigureServices method of Startup.cs.

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc(_version, new Info { Title = _applicationName, Version =   _version });
});

Finally, the application must enable JSON as well as UI endpoints for swagger within Configure method of Startup.cs so that end-users can interact with API methods through the Swagger UI.

// Enable Swagger JSON endpoint.
app.UseSwagger(); 
// Enable swagger-ui (HTML, JS, CSS, etc.)
app.UseSwaggerUI(c =>
{
     c.SwaggerEndpoint($"/swagger/{_version}/swagger.json",${_applicationName} {_version}"); 
});

The complete list of Startup.cs file is shown in Snippet 1.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;

namespace Api
{
    public class Startup
    {
        private string _applicationName;
        private string _version;
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            var config = Configuration.GetSection("ApplicationSetting").Get<ApplicationSetting>();
            _applicationName = config.Name;
            _version = config.Version;

          // Register the Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc(_version, new Info { Title = _applicationName, Version = _version });
                // Set the comments path for the Swagger JSON and UI.
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                c.IncludeXmlComments(xmlPath);
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
           
            // Enable Swagger JSON endpoint.
            app.UseSwagger();
            // Enable swagger-ui (HTML, JS, CSS, etc.), with the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint($"/swagger/{_version}/swagger.json", $"{_applicationName} {_version}");
            });
            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}
Snippet 1: Content of Startup.cs

Security

The API is secured and it makes much more sense to secure the Swagger UI and its endpoints so that unauthorized users cannot see the API documentation via Swagger UI. In this case, the usual authentication mechanism must be used. For example, a WS-Federation based authentication can be used to redirect users for authentication via a single sign-on page. Assuming the WS-Federation like authentication is placed, the Configure method must include the following code before 'app.UserSwagger();' line of code.

//Enable authentication
app.UseAuthentication();
app.Use(async (context, next) =>
   {
       if (!context.User.Identity.IsAuthenticated && context.Request.Path != "/signin-wsfed")
        {
           await context.ChallengeAsync(WsFederationDefaults.AuthenticationScheme);
        }
        else
        {
           await next();
         }
     });
    
// Enable Swagger JSON endpoint.
app.UseSwagger();

// Enable swagger-ui (HTML, JS, CSS, etc.), with the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
   {
      c.SwaggerEndpoint($"/swagger/{_version}/swagger.json", $"{_applicationName} {_version}");
   }); 

In addition, the ConfigureServices method must include the following code to trigger Authentication middleware related WsFederation.

//Authentication
services.AddAuthentication(options =>
{
   options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
   options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
   options.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddWsFederation(options =>
{
   options.Wtrealm = "ABC"; //use the site name
  options.MetadataAddress = "XYZ"; //use the server that does the SSO
});

With the authentication in place, the call to Swagger UI or Swagger JSON endpoint has to pass through the authentication middleware and thus, Swagger UI is only limited to rightful users.

Visualization

Upon completing the above four steps, swagger is ready to go. Browse through https://localhost:5001/swagger/v1/swagger.json to get the data in JSON (either in a browser or a client such as POSTMAN, Advanced Rest Client (ARC)). The returned JSON object provides the specification of the REST methods (separated by Controllers) and objects used in API. An example response is Snippet 2.

{
    "swagger": "2.0",
    "info": {
        "version": "v1",
        "title": "SmartStock API"
    },
    "paths": {
        "/api/Stock": {
            "get": {
                "tags": [
                    "Stock"
                ],
                "operationId": "GetStock",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "uniqueItems": false,
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Stock"
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Stock"
                ],
                "operationId": "PostStock",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "stock",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "$ref": "#/definitions/Stock"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "$ref": "#/definitions/Stock"
                        }
                    }
                }
            }
        },
        "/api/Stock/{symbol}": {
            "get": {
                "tags": [
                    "Stock"
                ],
                "operationId": "GetStock",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "symbol",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "uniqueItems": false,
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Stock"
                            }
                        }
                    }
                }
            }
        },
        "/api/Stock/{id}": {
            "delete": {
                "tags": [
                    "Stock"
                ],
                "operationId": "DeleteStock",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "string",
                        "format": "uuid"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "type": "boolean"
                        }
                    }
                }
            }
        },
        "/api/Values": {
            "get": {
                "tags": [
                    "Values"
                ],
                "operationId": "Get",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "uniqueItems": false,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    }
                },
                "deprecated": true
            },
            "post": {
                "tags": [
                    "Values"
                ],
                "operationId": "Post",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "value",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "deprecated": true
            }
        },
        "/api/Values/{id}": {
            "get": {
                "tags": [
                    "Values"
                ],
                "operationId": "Get",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "type": "string"
                        }
                    }
                },
                "deprecated": true
            },
            "put": {
                "tags": [
                    "Values"
                ],
                "operationId": "Put",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    },
                    {
                        "name": "value",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "deprecated": true
            },
            "delete": {
                "tags": [
                    "Values"
                ],
                "operationId": "Delete",
                "consumes": [],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "deprecated": true
            }
        }
    },
    "definitions": {
        "Stock": {
            "type": "object",
            "properties": {
                "id": {
                    "format": "uuid",
                    "type": "string"
                },
                "ticker": {
                    "type": "string"
                },
                "company": {
                    "type": "string"
                },
                "price": {
                    "format": "double",
                    "type": "number"
                }
            }
        }
    }
}
Snippet 2: JSON Response Sample

The Swagger UI is accessible by navigating to https://localhost:5001/swagger/index.html where a user can visualize the same data that was available in JSON response in an interactive format. This UI also supports the actual execution of the rest methods.

Figure 2: Visualizing API in Swagger UI

Testing

Swagger provides functionality to test the API methods without any tools. For example, clicking the GET (first tab in Figure 2.) expands the method. By clicking 'Try it Out' and then 'Execute', swagger triggers a call to 'get' method to /api/stock. Note that there is a single controller named 'StockController' in the demo application. The results are shown in the UI which can also be downloaded. Figure 3 shows a result screen for the get method. Any method exposed in the UI can be executed in UI itself, thereby giving us the ability to test the API directly from the API itself.

Figure 3: Testing of API methods in Swagger UI

Support to Attributes

Routing attributes, Http*attributes, and any other data annotations are supported by default. Figure 4 shows how the methods decorated with HttpGet, HttpPost, HttpDelete are reflected in the Swagger UI.

Figure 4: Swagger UI reflecting HTTP attributes

Swagger is in sync with attributes in .NET. For example, if a controller class is decorated with [Obsolete] attribute, the UI reflects the fact that the controller is 'obsolete'. Figure 5 shows that the ValuesController is marked 'Obsolete' and Swagger UI reflects the same and is not clickable. This feature becomes handy when API is phasing out certain functionality without breaking the working code.

Figure 5: Swagger UI reflecting the Obsolete attribute

Support XML Documentation

Swagger supports Documentation in UI with some configuration change. By adding the following lines of code in *.csproj file, the XML document is generated.

<Property Group>
     ....
     <GenerateDocumentationFile>
          true
     </GenerateDocumentationFile> 
     <NoWarn>
          $(NoWarn);1591
     </NoWarn>
<Property Group>
With the following lines of code in the Startup.cs, while registering, the UI shows the XML documentation.
services.AddSwaggerGen(c => 
      {   
          ...
            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); 
            c.IncludeXmlComments(xmlPath);
}
);

With the change to use XML path, the Swagger exposes the XML document in the UI. Note that documentation for the code can be made using XML comments where such comments are placed directly before the code block. For example, following code block shows XML comment for GetStock() method which simply displays the description of 'This method returns a list of stocks' for this method when viewed in Swagger UI as shown in Figure 6:

<summary>/// This method returns a list of stocks /// </summary>
[HttpGet] public IEnumerable<stock> GetStock()
{
    return Data.DbContext.GetStocks();
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 6: Swagger UI Shows Documentation.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Swagger is supported in all major browsers and works in both local or on the web environment. The look and feel of the UI are customizable.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

I showed previously how it works on a local machine. The Swagger UI works well on the cloud. The same application deployed to Azure, https://smartstockapi.azurewebsites.net/swagger/index.html works as it worked in my local machine.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 7: Swagger UI in Application Hosted in Cloud (Azure)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Resources/Materials/References

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here