Click here to Skip to main content
14,021,484 members
Click here to Skip to main content
Add your own
alternative version

Stats

7.3K views
9 bookmarked
Posted 16 Dec 2018
Licenced CPOL

Document and Test API with Swagger UI

, 17 Dec 2018
Rate this:
Please Sign up or sign in to vote.
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 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 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 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.

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 before 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Benktesh Sharma
United States United States
Benktesh is member of an amazing IT Lending development team at Cuna Mutual Financial Group (CMFG) in Madison WI where he is a software developer/architect. His main focus when coding is quality, simplicity and scalability and he is experienced in a broad range of subjects (software design, software development process, project management, testing, UI, multithreading, database, mobile development, and more).

Outside of professional work, there is always some side projects he is working on such as simulation modeling of forest management and carbon quantification and writing articles both technical and non-technical. While not coding, he is occupied with reading, watching tv shows, trading stocks, and discuss climate change, global warming, emission trading, and traveling to meet people, place and culture.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionOauth2 Pin
Daniel R. Przybylski18-Dec-18 9:36
memberDaniel R. Przybylski18-Dec-18 9:36 
AnswerRe: Oauth2 Pin
Benktesh Sharma20-Dec-18 1:19
memberBenktesh Sharma20-Dec-18 1:19 
QuestionSwaggerHub may build server stub directly into your Github repo Pin
Jose Motta18-Dec-18 9:19
memberJose Motta18-Dec-18 9:19 
AnswerRe: SwaggerHub may build server stub directly into your Github repo Pin
Benktesh Sharma20-Dec-18 1:15
memberBenktesh Sharma20-Dec-18 1:15 
QuestionGood details on swagger, but for API automation Pin
N Fleet16-Dec-18 23:14
memberN Fleet16-Dec-18 23:14 
AnswerRe: Good details on swagger, but for API automation Pin
Benktesh Sharma21-Dec-18 0:44
memberBenktesh Sharma21-Dec-18 0:44 
QuestionNeeds formatting Pin
Richard MacCutchan16-Dec-18 22:40
protectorRichard MacCutchan16-Dec-18 22:40 
AnswerRe: Needs formatting Pin
Benktesh Sharma17-Dec-18 2:24
memberBenktesh Sharma17-Dec-18 2:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190417.4 | Last Updated 17 Dec 2018
Article Copyright 2018 by Benktesh Sharma
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid