Jeremy Likness
Jeremy Likness
Empowering developers to be their best.
šŸ“… Jan 4, 2018 šŸ•˜ 7 min read šŸ’¬ 1391 words

Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0 Bonus: Swagger

Swashbuckle your API to OpenAPI goodness with a little Swagger

Part of the series: Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0

You are viewing a limited version of this blog. To enable experiences like comments, opt-in to our privacy and cookie policy.

Alas, all things must come to an end. This is it: the bonus round of this Web API design series on ASP. NET Core. In case you missed any of the previous artfully crafted stories in this series, I present the full list for easy navigation:

  1. Intro and Content Negotiation
  2. HATEOAS
  3. Exceptions
  4. Concurrency
  5. Security
  6. Bonus (I told you weā€™d make it here eventually)

I believe an easy way to understand Swaggeris to begin with a little history. In the dim recesses of the past, back in the days when JavaScript was considered annoying and didnā€™t run on servers, and everyone believed that Extensible Markup Language (XML) would change the world, there existed a complicated protocol named the Simple Object Access Protocol, or SOAP for short. Although there really wasnā€™t anything simple about SOAP, it paired well with the Web Services Description Language, or WSDL (we pronounce it wizz-dull).

The WSDL empowered tools to understand what SOAP services looked like, enabling what .NET developers refer to as the right-click experience. It works on all platforms (albeit, not by the same mechanism, as we know some people have those funny mouses with only one button): you simply point the right tool at the WSDL interface and it auto-generates the client code needed to connect to and interact with the service, including any data and models that are part of the definition. Pretty nifty!

Presenting Exhibit A: discovery of the ā€œDilbertā€ service

Presenting Exhibit A: discovery of the ā€œDilbertā€ service

The rise of smartphones and open source software completely transformed the landscape of web services. I imagine the first mobile phone tried to parse the XML returned by a WSDL call and threw up itā€™s antennae in exasperation while crying, ā€œEnough!ā€ Every phone has a web browser, and that means every phone has the ability to make HTTP/HTTPS requests and run JavaScript. That made it a simple choice to move to REST. Mobile trumped everything and soon developers all over the world tossed XML to the curb and increased their cool factor by hanging out with JSON.

Unfortunately, unless REST is implemented with HATEOAS, it doesnā€™t offer metadata or discovery services.

Enter the OpenAPIspecification. In a nutshell, and I quote directly from the repository:

The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for REST APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic. When properly defined via OpenAPI, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interface descriptions have done for lower-level programming, the OpenAPI Specification removes guesswork in calling a service.

This is great, but what it doesnā€™t do is remove guesswork from implementing the specification itself. Thatā€™s when Swagger enters the picture:

Swagger is the worldā€™s largest framework of API developer tools for the OpenAPI Specification(OAS), enabling development across the entire API lifecycle, from design and documentation, to test and deployment.

So there you have it: OpenAPI is the specification, and Swagger is a set of tools that facilitate the implementation. The tools turn out to be quite powerful. Iā€™ve found there are fundamentally two approaches to implementing the specification via the tools. The first, like Test-Driven Development (TDD), involves designing the API first. You use a site like SwaggerHub to design your API, include your data models, expected return values, potential exceptions, authentication definitions, and more. Then you use additional tools that read the specification (which, coincidentally, is in a JSON format) and generate boilerplate server-side and client-side code.

Iā€™ll leave that experience for you to try out.

Then thereā€™s the developers who write their code first and throw in tests as an afterthought. You can do the same thing with Swagger, only the analogy breaks down because itā€™s actually a valuable workflow to design your API in code and generate the specification dynamically. In other words, what Iā€™m about to show is generally accepted in elite circles, whatever those might be. The good news is that it is incredibly easy to add these specifications to your legacy code.

You can dig into the specification yourself, but to summarize here are some simple pictures I created. I picked the color palette myself. Later Iā€™ll show you the actual JSON and you will see the correlation between these images and the document. You might even slap your palm on your forehead and go, ā€œWow!ā€

OpenAPI specification part 1

OpenAPI specification partĀ 1

OpenAPI specification part 2

OpenAPI specification partĀ 2

OpenAPI specification part 3

OpenAPI specification partĀ 3

OK, enough talk. Letā€™s see some code! Starting with our simple ā€œtodoā€ application, first I add the Swagger tool for ASP. NET Core, Swashbuckle.

dotnet add package Swashbuckle.AspNetCore

Next, add a using statement at the top of the Startup.cs to bring in the new library:

using Swashbuckle.AspNetCore.Swagger;

Finally, add the generator to the services and configure the application to use Swagger and activate the Swagger UI (that will provide an interactive browser-based explorer).

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
    services.AddMvc();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
    });
}

public void Configure(IApplicationBuilder app)
{
    // Enable middleware to serve generated Swagger as a JSON endpoint.
    app.UseSwagger();

    // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
    app.UseSwaggerUI(c =>
    {
      c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    });
    app.UseMvc();
}

Thatā€™s really all there is to it! Running the application performs exactly as before, with a few exceptions. First, you can access the Swagger UI by navigating to [http://localhost:5000/swagger](http://localhost:5000/swagger)

Such a pretty list of operations

Such a pretty list of operations

Not only do you get the list of API endpoints, you can expand individual ones and even test the API right from your browser! (Sorry, Postman).

Look, I just fetched a ā€œtodoā€ item right from the browser!

Look, I just fetched a ā€œtodoā€ item right from theĀ browser!

If you navigate to the definition endpoint: [http://localhost:5000/swagger/v1/swagger.json](http://localhost:5000/swagger/v1/swagger.json)

ā€¦you get the specification. This can be loaded to other Swagger tools that may then generate clients in multiple languages on various platforms. Pretty cool, no? Hereā€™s what is generated by default from the ā€œtodoā€ app. Uh, yeah. Itā€™s a bit wordy.

{
    "swagger": "2.0",
    "info": {
        "version": "v1",
        "title": "My API"
    },
    "basePath": "/",
    "paths": {
        "/api/Todo": {
            "get": {
                "tags": [
                    "Todo"
                ],
                "operationId": "ApiTodoGet",
                "consumes": [],
                "produces": [
                    "text/plain",
                    "application/json",
                    "text/json"
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/TodoItem"
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Todo"
                ],
                "operationId": "ApiTodoPost",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "item",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "$ref": "#/definitions/TodoItem"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/api/Todo/{id}": {
            "get": {
                "tags": [
                    "Todo"
                ],
                "operationId": "ApiTodoByIdGet",
                "consumes": [],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int64"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            },
            "put": {
                "tags": [
                    "Todo"
                ],
                "operationId": "ApiTodoByIdPut",
                "consumes": [
                    "application/json-patch+json",
                    "application/json",
                    "text/json",
                    "application/*+json"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int64"
                    },
                    {
                        "name": "item",
                        "in": "body",
                        "required": false,
                        "schema": {
                            "$ref": "#/definitions/TodoItem"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Todo"
                ],
                "operationId": "ApiTodoByIdDelete",
                "consumes": [],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int64"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        }
    },
    "definitions": {
        "TodoItem": {
            "type": "object",
            "properties": {
                "id": {
                    "format": "int64",
                    "type": "integer"
                },
                "name": {
                    "type": "string"
                },
                "isComplete": {
                    "type": "boolean"
                }
            }
        }
    },
    "securityDefinitions": {}
}

You can enhance your API definitions by providing attributes that describe more information such as additional response codes, and you can edit the auto-generated definition to improve its fidelity. Either way, I think youā€™ll agree that Swagger is a great way to document your REST APIs and make them easier to discover and consume by clients. If ā€œclientsā€ means internal applications, you can simply turn off the Swagger definition in production and use it during development.

And that, as they say, is a wrap.

In case you didnā€™t catch the rest of the series, hereā€™s your easy navigation:

  1. Intro and Content Negotiation
  2. HATEOAS
  3. Exceptions
  4. Concurrency
  5. Security
  6. Bonus (what do you expect? This is itā€Šā€”ā€Šthere is no more!)

Regards,

Jeremy Likness

Do you have an idea or suggestion for a blog post? Submit it here!
comments powered by Disqus

Part of the series: Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0

  1. Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0 Part 1: Content Negotiation
  2. Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0 Part 2: HATEOAS
  3. Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0 Part 3: Exceptions
  4. Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0 Part 4: Optimistic Concurrency
  5. Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0 Part 5: Security
  6. Five RESTFul Web Design Patterns Implemented in ASP.NET Core 2.0 Bonus: Swagger