Creating Discoverable HTTP APIs with ASP.NET Core 5 Web API

Brady Gaster

This month, we’ll be focusing on building HTTP APIs with .NET 5. We’ll explore a myriad of different tools, technologies, and services that make your API development experience more delightful. Each week, we’ll release a new post on this blog that goes into a separate area of building HTTP APIs with .NET, focusing mostly on using ASP.NET Core 5 Web API and the OpenAPI Specification together to build, publish, consume, and re-use well-described HTTP APIs. Here’s a glance at the upcoming series on building HTTP APIs using .NET on the ASP.NET team blog:

Whether APIs are your product, or they’re a facet of the topology you build by default in your daily consulting work or freelancing, the process of building, testing, and integrating APIs can appear daunting. The posts in this series will show you how .NET is a great choice, as it offers RESTful, serverless, and more modern gRPC and HTTP2/3 investments. By the end, you’ll know some new techniques and conventions, and have some sample code and open-source projects to follow.

Thinking design-first

If you’ve ever referenced a SOAP Web Service within Visual Studio using right-click Add Web Reference or, once WCF appeared, Add Service Reference, you know the joy brought to your development experience by the tools and their support for a standard description format – the Web Service Definition Language (WSDL).

The OpenAPI Specification has evolved as the leading industry convention for describing standard request/response HTTP APIs, and a myriad of tools, open-source packages, and frameworks have been built atop ASP.NET Web API to make the process of building HTTP APIs simple. Whilst OpenAPI isn’t as strict or verbose as WSDL, the relaxed nature of the language makes for a wide variety of misuses and missed opportunities.

The .NET community is still rich with an ecosystem of open-source packages and tools, some of which we use in the ASP.NET templates and Visual Studio tooling. In the ASP.NET Core 5 Web API space, there are a handful of packages developers can use to get going with an end-to-end development experience using OpenAPI specifications as a contract between the API and the clients:

  • Swashbuckle.AspNetCore – The most popular OpenAPI-generating package for ASP.NET Core developers. Used not only by the Swagger Codegen project, but also by the ASP.NET Core 5 Web API templates (catch the HTTP APIs session from .NET Conf where we highlight these updates to the Web API template). Swashbuckle emits Swagger/OpenAPI 2.0, 3.0, and 3.0 YAML, and can output the Swagger UI test page to make testing and documenting your APIs easy.
  • NSwag – NSwag is another fantastic choice for generating OpenAPI documents from ASP.NET Core 5 Web API, but the NSwag team has an entire toolchain complete with NSwagStudio.
  • Microsoft.dotnet-openapi – .NET Global Tool that can generate C# client SDK code for an OpenAPI specification.

A well designed API is so much nicer to develop, maintain, and consume. By following a few simple conventions when building Web APIs that use these packages to describe your APIs, your APIs will be much more discoverable, integrate with other products and cloud services more easily, and in general, offer more usage scenarios.

The sample project

This post will use a sample Visual Studio Solution. In the solution you’ll find a simple Web API project for Contoso Online Orders. We’ll build more onto the API over time, but just to give you a glimpse at the API’s shape, take a look at the Swagger UI page from the Web API project’s Debug experience.

Swagger UI page

The solution comes with the API non-optimized for discoverability. Throughout the steps in this post, you’ll be shown how to use any of the Visual Studio family of IDEs to make changes to the projects so you’ll see a before-and-after experience of the API as it was at first, then improved over time by small incremental changes.

The changes we’ll make can be summarized as:

  • Making the OpenAPI specification more concise
  • Inform consumers or integrators of all the potential request and response shapes and statuses that could happen
  • Ensure that OpenAPI code generators and OpenAPI-consuming services can ingest my OpenAPI code and thus, call to my API

With that, we’ll jump right in and see how some of the attributes built in to ASP.NET Core 5 Web API can make your APIs concise, right out of the box.

Produces / Consumes

The JSON code for the Web API project is, by default, rather verbose and, ironically, not very informative about what could happen when the API is called in various states. Take this orders operation, shown here. The C# code in my Web API controller will always output objects serialized in JSON format, but the OpenAPI specification is advertising other content types, too.

{
  "paths": {
    "/orders": {
      "get": {
        "tags": [
          "Admin"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Order"
                  }
                }
              },
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Order"
                  }
                }
              },
              "text/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Order"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

ProducesAttribute is used to specify the content type of the output that will be sent by the API, and the corresponding ConsumesAttribute is used to specify the request content types the API expects to receive. As described in the docs on formatting Web API output, Produces and Consumes filters by either specifying the content-type you want to output:

[Produces("application/json")]
[Consumes("application/json")]

As shown in the sample project corresponding to this blog series, you can also use the MediaTypeName class to make it simpler to use well-known values for the media type. With the sample controllers, we want every request object and response object to be serialized as JSON. To facilitate this at the controller level, each controller is decorated with both the Produces and Consumes attributes. To each attribute is passed the well-known property MediaTypeNames.Application.Json, thus specifying that the only content type our API should use for both directions is application/json.

    [Route("[controller]")]
    [ApiController]
#if ProducesConsumes
    [Produces(MediaTypeNames.Application.Json)]
    [Consumes(MediaTypeNames.Application.Json)]
#endif
    public class AdminController : ControllerBase
    {
        // controller code
    }

Since the code in the sample project is built to check for certain symbles using compiler directives, you can easily tweak the build configuration to include the Defined Constant value of ProducesConsumes to turn on the attributes in the Web API sample project code. In Visual Studio for Mac, constants can be added by double-clicking a .csproj file in the Solution Explorer to open the project properties window.

Adding ProducesConsumes to the compiler settings.

Now, when the Web API project is re-built and run, the OpenAPI specification is considerably smaller, due in large part to the removal of the unnecessary request and response content nodes. The updated JSON, reflecting this change, now only shows the application/json content type, thus making the API specification much more compact.

{
  "paths": {
    "/orders": {
      "get": {
        "tags": [
          "Admin"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Order"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

HTTP Response Codes

Web API developers can make use of a variety of Action Result inheritors to send the appropriate HTTP status code to the calling client in addition to the object or operation implemented by the API. In the GetProduct method, for example, the code tries to get a product by ID. If the product is found, it is returned along with an HTTP OK via the Ok result. If the product isn’t found, the API returns an HTTP 404, with no payload.

[HttpGet("/products/{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
    var product = StoreServices.GetProduct(id);

    if(product == null)
    {
        return NotFound();
    }
    else
    {
        return Ok(product);
    }
}

However, the OpenAPI JSON for this method only shows the 200 response code, the default behavior for an HTTP GET. OpenAPI supports the notion of communicating all potential response codes an API could return, so we’re missing out on an opportunity to inform potential consumers or code generators on “what could happen” when the API method is called.

"/products/{id}": {
  "get": {
    "tags": [
      "Shop"
    ],
    "parameters": [
      {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer",
          "format": "int32"
        }
      }
    ],
    "responses": {
      "200": {
        "description": "Success",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Product"
            }
          }
        }
      }
    }
  }
}

Web API Conventions

Web API conventions, available in ASP.NET Core 2.2 and later,include a way to extract common API documentation and apply it to multiple actions, controllers, or all controllers within an assembly. Web API conventions are a substitute for decorating individual actions with [ProducesResponseType]. Since API Conventions are extensible, you could write your own to enforce more granular rules if needed. Common use cases of conventions would be to:

  • Define the most common return types and status codes returned from a specific type of action.
  • Identify actions that deviate from the defined standard.

The sample Web API project’s Program.cs file is decorated with the API Convention attribute – this is an approach that will impact the output of every Web API controller in the assembly, but you can apply conventions more granularly if desired. See the documentation on Web API conventions for more details.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

#if ApiConventions
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
#endif

namespace ContosoOnlineOrders.Api
{
    public class Program
    {

For this second compiler change, here’s how to do the same thing in Visual Studio. You can also double-click any .csproj file in the Solution Explorer and manually enter it (see below).

Add the ApiConventions build property

Once you’ve made the change, re-building and running the Web API project will result with the OpenAPI specification being equipped with response code details for each of the API operations.

"/products/{id}": {
  "get": {
    "tags": [
      "Shop"
    ],
    "parameters": [
      {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer",
          "format": "int32"
        }
      }
    ],
    "responses": {
      "404": {
        "description": "Not Found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "default": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "200": {
        "description": "Success",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Product"
            }
          }
        }
      }
    }
  }
}

Emit OpenAPIs operationId in Web API

In the OpenAPI specification, operationId is defined as “an optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API.” The operationId attribute is used essentially to provide a explicit string-based identifier for each operation in an API.

Whilst the operationId attribute isn’t required according to the OpenAPI Specification, including it in your APIs offers significant improvements in the API Consumption experience – documentation, code-generation, and integration with a myriad of services.

Take a look at a condensed version of the OpenAPI specification. This snapshot summarizes the API to the various endpoints, verbs, and operations offered by the API.

{
  "paths": {
    "/orders": {
      "get": {
        "tags": [
          "Admin"
        ]
      },
      "post": {
        "tags": [
          "Shop"
        ]
      }
    },
    "/orders/{id}": {
      "get": {
        "tags": [
          "Admin"
        ]
      }
    },
    "/products/{id}/checkInventory": {
      "put": {
        "tags": [
          "Admin"
        ]
      }
    },
    "/products": {
      "post": {
        "tags": [
          "Admin"
        ]
      },
      "get": {
        "tags": [
          "Shop"
        ]
      }
    }
  }
}

Each of the Web API Action Methods in the sample project is decorated with two variations of the Attribute Route. The first, wrapped in the OperationId compiler symbol, results in the name of the C# Action Method being automatically set as the operationId value. The Name property, when passed to the constructor of each HTTP verb filter, is used as the value of the operationId attribute in the generated OpenAPI specification document. The second lacks the Name property, which results in the operationId attribute value being omitted.

#if OperationId
[HttpGet("/orders", Name = nameof(GetOrders))]
#else
[HttpGet("/orders")]
#endif
public async Task<ActionResult<IEnumerable<Order>>> GetOrders()
{
    return Ok(StoreServices.GetOrders());
}


#if OperationId
[HttpPost("/orders", Name = nameof(CreateOrder))]
#else
[HttpPost("/orders")]
#endif
public async Task<ActionResult<Order>> CreateOrder(Order order)
{
    try
    {
        StoreServices.CreateOrder(order);
        return Created($"/orders/{order.Id}", order);
    }
    catch
    {
        return Conflict();
    }
}

The 3rd and final compiler switch you’ll add to the sample project activates operationId generation. If you’re using Visual Studio for Mac or Windows, use one of the techniques shown earlier to set it. Or, if you’re in Visual Studio Code or another text editor, just edit the .csproj files in the solution (both of them) to include the operationId value (and to expect it during consumption and code-generation).

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  <DefineConstants>TRACE;DEBUG;NET;NET5_0;NETCOREAPP;ProducesConsumes;ApiConventions;OperationId</DefineConstants>
</PropertyGroup>

Once the OperationId symbol is set, the Action Methods on each of the Web API controllers emits operationId attribute values. Considering the condensed version of the OpenAPI specification from earlier, here’s the new OpenAPI spec inclusive with the operationId attributes.

{
  "paths": {
    "/orders": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "GetOrders"
      },
      "post": {
        "tags": [
          "Shop"
        ],
        "operationId": "CreateOrder"
      }
    },
    "/orders/{id}": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "GetOrder"
      }
    },
    "/products/{id}/checkInventory": {
      "put": {
        "tags": [
          "Admin"
        ],
        "operationId": "CheckInventory"
      }
    },
    "/products": {
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "CreateProduct"
      },
      "get": {
        "tags": [
          "Shop"
        ],
        "operationId": "GetProducts"
      }
    }
  }
}

Benefits in the Generated SDK Code

Human readability isn’t the only benefit. Code generators like the Microsoft.dotnet-openapi tools, NSwag, and AutoRest operate more gracefully when OpenAPI specifications include the operationId attribute.

Later in this blog series, we’ll take a look at how Visual Studio Connected Services makes use of the Microsoft.dotnet-openapi tools to streamline C# client SDK code generation with one click. For now, imagine inheriting code for an ordering-process test that looked like the code below.

// create a product
await apiClient.ProductsAllAsync(new CreateProductRequest
{
    Id = 1000,
    InventoryCount = 0,
    Name = "Test Product"
});

// update a product's inventory
await apiClient.CheckInventory2Async(1,                           
new InventoryUpdateRequest
    {
        CountToAdd = 50,
        ProductId = 1000
    });

// get all products
await apiClient.ProductsAsync();

// get one product
await apiClient.Products2Async(1000);

// create a new order
Guid orderId = Guid.NewGuid();

await apiClient.OrdersAsync(new Order
{
    Id = orderId,
    Items = new CartItem[]
    {
        new CartItem { ProductId = 1000, Quantity = 10 }
    }
});

// get one order
await apiClient.Orders2Async(orderId);

// get all orders
await apiClient.OrdersAllAsync();

// check an order's inventory
await apiClient.CheckInventoryAsync(orderId);

// ship an order
await apiClient.ShipAsync(orderId);

Without comments, most of the methods – especially OrdersAsync and Orders2Async – are discoverable only when looking at their arguments. This generated code suffers from poor readability or discoverability. This code was also generated from an OpenAPI spec lacking in operationId values. So, the code generator had to make a host of assumptions based on the value of the tags, the name of the operation, and so on.

But once the OpenAPI specification has been augmented with values for each of the operations’ operationId attribute, the code generator has more information and can generate a more concise SDK that any developer can use and discover with little effort.

// create a product
await apiClient.CreateProductAsync(new CreateProductRequest
{
    Id = 1000,
    InventoryCount = 0,
    Name = "Test Product"
});

// update a product's inventory
await apiClient.UpdateProductInventoryAsync(1,
    new InventoryUpdateRequest
    {
        CountToAdd = 50,
        ProductId = 1000
    });

// get all products
await apiClient.GetProductsAsync();

// get one product
await apiClient.GetProductAsync(1000);

// create a new order
Guid orderId = Guid.NewGuid();

await apiClient.CreateOrderAsync(new Order
{
    Id = orderId,
    Items = new CartItem[]
    {
        new CartItem { ProductId = 1000, Quantity = 10 }
    }
});

// get one order
await apiClient.GetOrderAsync(orderId);

// get all orders
await apiClient.GetOrdersAsync();

// check an order's inventory
await apiClient.CheckInventoryAsync(orderId);

// ship an order
await apiClient.ShipOrderAsync(orderId);

Summary

This first post, whilst somewhat theoretical and design-oriented, is very important to the subsequent phases of building and using HTTP APIs. With these simple steps taken early, your OpenAPI specification document will more thoroughly and concisely describe your API and make it simpler for consumers to use.

We look forward to exploring the world of HTTP APIs with .NET with this and other projects this month! Feel free to provide feedback on the article, the series, and as always, feel enabled to use our GitHub repositories to submit issues and ideas if you’re inspired to make the product better through feedback.

14 comments

Discussion is closed. Login to edit/delete existing comments.

  • Николай Чеботов 0

    You can also use NSwag-based Visual Studio extension to generate client code.

  • Ruslan Fedoseenko 0

    What do you mean by discoverable? As for me first thoughts when I read the post heading is that some kind of service discovery will be discribed.

    • Ian Yates 0

      That the client-side api that’s generated works well with intellisense and is thus discoverable that way.

      Ideally the consumer – the client side developer – gets an API developed for them that means they have readable code, and can intuitively work out what to do due to well-named endpoints and typed parameters & return values

      • Scott Anderson 0

        I thought the same with this article being on service discovery, not API client generation. That also seems to be the general consensus for web service discovery even for SOAP based services, like this and many other articles refer to, https://en.wikipedia.org/wiki/Web_Services_Discovery.

        • Brady GasterMicrosoft employee 0

          I appreciate that feedback from all of you – I debated that title a bunch, thinking “will folks think of service discovery?” I’d be open to changing the title (but not the slug) to something about “better descriptions” or “more descriptive specs,” – none of that seemed to work as well.

          Apologies for confusing the message with that title – hope the content made sense once that could be overlooked.

          While we’re at it – are there any service discovery options with which folks would be interested? We are writing a series on APIs, not just a one-off post, so I’m open if there are any service discovery techniques or options folks would like us to explore with this series of a future one.

  • Michael Taylor 0

    OpenAPI is awesome and we use it for all our documentation but Swashbuckle is an outdated implementation based upon the original Swagger spec (and updated with some OpenAPI features) and is a complete nightmare to use. I personally would not recommend using it if there are other tools available.

    • Nobody uses the Swagger UI for real APIs. It doesn’t scale well nor is it flexible for anything beyond simple APIs. It took several releases before it even supported OAuth2. Postman and similar tools are the approach people use in my experience, provide far more flexibility and can import any OpenAPI spec easily.
    • Generating your OpenAPI spec at runtime makes no sense to me. Once compiled it will not change so it should be a build time generation, not on first use. Swashbuckle doesn’t support this. There are third party build tasks that can do this but it requires running your code because Swashbuckle uses reflection.
    • Swashbuckle is very much behind the latest OpenAPI spec such that you cannot properly document things using the newer spec (or was last year). Postman does a better job here albeit the process is manual.
    • Swashbuckle requires a mix of reflection and doctags to work correctly (or at least it did when I used it several years ago). It gets type information and some of the custom attributes mentioned in this article via reflection but descriptions (historically) come from doctags. That means you have to ship your comment files as well. Makes no sense whatsoever to me. We wrote a documentation generator that uses only reflection to work around this but it is added work we shouldn’t have had to do to begin with.
    • For anything beyond simple APIs and models you have to write custom extenders which requires understanding how Swashbuckle works. We use some value types (think strings) that have stringent formatting requirements. Swashbuckle cannot do anything with these so creating a schema extension is the only solution. This is tedious and completely breaks the self-documenting approach to code. OpenAPI supports this easily.
    • The last time I looked Swashbuckle can only generate JSON files. YAML seems to be the preferred approach for OpenAPI specs. Every client generator supports both and YAML is just easier to read. Given it is a description language being a little larger than JSON isn’t important when generating a client.

    I agree with the article’s recommendation of using OpenAPI for documenting REST APIs. I just cannot possibly recommend Swashbuckle. I’m really surprised that MS is trying to promote this tool over writing their own. I guess it is a lot like MS pushing the AutoRest library instead of getting everything working with the standard HttpClient.

    • Brady GasterMicrosoft employee 0

      It looks like we’ve had a varied experience with some of these, and it sounds like you’ve been building APIs with Web API for some time. I’d actually be interested in how we could make the experience better for you and other advanced customers, but for a lot of what you’ve said, we’ve found that new Web API customers and folks who are just getting started truly do benefit from some of the things it sounds you’ve had a different experience using. Specifically:

      • I’ve lost count of how many customers I’ve spoken with who do use Swagger UI in their development and testing phases. They also use Postman, or the HttpRepl, or RestClient in VS Code. But the lion’s share of folks who were new to Web API or using it in a project reported a ton of delight with having the Swagger UI appear as their start page versus the response from an HTTP GET request. It’s purely a dev-time thing, hence we even wrapped the middleware and service code that outputs the UI (and the OpenAPI JSON) within “IsDevelopment()” blocks. But you’re right about Postman – it is a fantastic tool (as are some of the others I mentioned).

      • It makes sense for a lot of customers (and made sense in the ASMX/SVC days, too). Whilst I’d concur with you that I’d prefer the OpenAPI spec be built at build/publish time, a lot of customers want that dynamic URL, especially during testing. Swashbuckle in fact does support build-time generation via the Swashbuckle.Cli .NET Global Tool – i’m using it in the sample code, in fact, to generate the OpenAPI spec at build time so the NSwag-fueled build-time SDK generation can also work, as it requires that static file to do what it does at compile time. So yes, Swashbuckle, NSwag, and also Carter project provide run-time OpenAPI, but Swashbuckle.CLI also enables it at build time. I think NSwag can do that too, but don’t quote me on it as I’ve not investigated the build-time capabilities of NSwag at depth [yet].

      • I’m interested in your comment about Swashbuckle lacking the latest support and encourage you to reach out to me to discuss this more. I’d like to know what you’ve experienced here. Swashbuckle, like NSwag and the upcoming OpenAPI bindings for Azure Functions all offer the ability to adhere to the Swagger 2.0 spec OR to up-level your output to OpenAPI 3 JSON (or YAML). If there are other nuanced behaviors Swashbuckle is lacking in regards to generating OpenAPI 3.0, let’s definitely get together as I’d like to know what your needs are there. We work closely with the folks at Swashbuckle AND the folks at NSwag, so if we have a collective opportunity to improve the experience, I’m all for syncing up with you 1:1 to learn more about what you’re experiencing.

      • NSwag and Swashbuckle (and a few other implementations out there) all rely on reflection. Some non-.NET packages in other communities do the same thing. I wholly agree with you that mixing the XML commentary and the C# “way” is sub-optimal, and I try to make my API “readable” or “describable” so that the documentation isn’t absolutely required too. I believe that Swashbuckle has attributes you can use to infer documentation too instead of using the XML comments, but I could be incorrect on this – I’ve not looked at that component of it (or NSwag) in some time. We’re in agreement here, I’d prefer to not have to mix the XML comments AND the C# code. If I had it my way, it’d all be in C#. FWIW, I think that was one of the earlier benefits and draws to Swashbuckle – that folks could use their XML comments to inject docs into the Swagger UI; in fact that’s precisely why I used either the first time. But yeah – we’re in agreemement here. I’d much prefer “one way” to add docs, and I’d personally rather have an attribute than a /// comment.

      • Not sure I have experienced your “complex models” thing, but I have experienced interesting issues with both of the implementations when it comes to polymorphism. I think that’s a whole different conversation, though. 🙂

      • SB can do Yaml – you can do that now. I’d encourage you to take a peek at it.

      Thanks for the great conversation and feedback on the post. Again, if you’re interested in syncing up and providing this feedback or showing me some of the issues you’ve seen, I’m all for it. You know where to find me. 🙂

      • Michael Taylor 0

        Yes it does appear that Swashbuckle has been extended to include some of the features in OpenAPI 3.x but it seems like a lot of these require you pull in yet more packages to do so.

        • Using Swashbuckle specific attributes to describe our API-agnostic types isn’t going to work. Swashbuckle needs to be able to read standardized attributes, we would need to define API-specific models or it has to support surrogates (which adds more work for us). The API is only 1 client of the types and taking a dependency on Swashbuckle outside the API layer doesn’t make sense.
        • Some APIs use HTTP headers for information. I don’t see any way to convey this in Swashbuckle. You can define interceptors but that is for Swagger UI.
        • I see that Swashbuckle now supports examples for schemas but, again, mixing doctags and attributes isn’t a good solution to me.
        • Maybe I missed it but I don’t see any support for format on schemas so if I have a string type that has a stringent formatting requirement then I don’t see how to represent that without an example.
        • OpenAPI allows for media-type differences so I could return one structure for JSON but another for XML. I don’t see how I’d do that in Swashbuckle
        • At least in the past I think Swashbuckle didn’t like async methods and cancellationtokens so we had to write a custom provider(?) to strip them out of the reflected data.

        I’m glad Swashbuckle is getting some attention as I believe OpenAPI is a great approach but it should be built into the API framework, it should be easily extensible, and it needs to be kept up to date. When we were working with OpenAPI 2 just a few years back Swashbuckle had no support, or YAML either. It seems like it is getting attention now and that is good. I also particularly liked how Swashbuckle had extension points. The problem was that you had to dig into the source code to understand how they worked to implement your own. The validation on Swashbuckle also isn’t great. We had some issues getting it to generate a spec that was valid in OpenAPI 2 but it’s been a while so maybe it is better now.

    • Rico Suter 0

      I can only speak for NSwag (main maintainer of it). Here my answers:

      • OAuth2: Swagger UI is only one frontend and NSwag/Swashbuckle is more about the spec itself, it’s up to you how to actually access the APIs. For local development Swagger UI is nice and there I personally also turn off auth for better dev experience.
      • OpenAPI spec at runtime: NSwag supports generating the spec at build time, so it is part of the repo and PR where you can also review public API changes (that’s how I do it in my workflows).
      • Behind the latest OpenAPI: NSwag supports OAI 3.0 and the generated spec is fully valid and supported by other tools (eg API Management). If newer features are not needed to describe the API why would the generator need them? Of course the model supports them and you can manually modify them at generation time if needed (operation/document processors).
      • Mix of reflection and doctags to work correctly: The goal of NSwag/Swashbuckle is to rely on ASP.NET Core attributes as much as possible and only where needed add attributes on top of them. Of course we are speaking here from a C#/API first approach (not schema first).
      • Beyond simple APIs and models you have to write custom extenders: Maybe that is true but than you should ask yourself whether it’s a good idea to implement very complicated APIs where for example the input changes the structure of the output (which cannot be statically be described by OAI).
      • Swashbuckle can only generate JSON files: Cannot speak for Swashbuckle but NSwag supports JSON and YAML as output/input.
  • Tory Berra 0

    Id also like to mention (shameless plug!) there is a dotnet new template out there called Boxed.AspNetCore which scaffolds out an API that uses almost all the approaches this article mentions. Plus tons of other features like versioning, logging, caching, compression, docker, kestrel security options and much more. https://github.com/Dotnet-Boxed/Templates/blob/main/Docs/API.md

  • Goti Ankit 0

    Hello Brady,

    I am using swagger

    Swashbuckle.AspNetCore(5.5.0), Microsoft.OpenApi(1.2.3.0).

    I am testing with fortellis CLI, From that, I am getting public API errors like I shared below, I am able to resolve major errors or warnings but I am not able to resolve
    “operation objects should declare a produces property”. and “operation objects should declare a consumes property”,

    That is because in swagger generation response we do not have produces and consumes properties found. I used

    [ProducesResponseType(typeof(Model), 200)]

    it seems correct but does not add element for produces and consumes.

    [31merror[39m: operation objects should declare a produces property.
    [34m–>[39m feb24_01.yaml:7341:9
    [34m |
    [39m[34m7341 |[39m get:
    [34m |
    [39m
    [31merror[39m: operation objects should declare a consumes property.
    [34m–>[39m feb24_01.yaml:7341:9
    [34m |
    [39m[34m7341 |[39m get:
    [33mwarning[39m: Operation description must be a present and non-empty string.
    [34m–>[39m feb23_06.yaml:6952:9
    [34m |
    [39m[34m6952 |[39m get:
    [34m |
    [39m
    [33mwarning[39m: Operation should have an operationId.
    [34m–>[39m feb23_06.yaml:6952:9
    [34m |
    [39m[34m6952 |[39m get:
    [34m |
    [39m
    [33mwarning[39m: operation objects should declare a operationId property
    [34m–>[39m feb23_06.yaml:6952:9
    [34m |
    [39m[34m6952 |[39m get:
    [34m |
    [39m
    [33mwarning[39m: operation objects should declare a summary property
    [34m–>[39m feb23_06.yaml:6952:9
    [34m |
    [39m[34m6952 |[39m get:
    [34m |
    [39m
    [33mwarning[39m: operation objects should declare a description property.
    [34m–>[39m feb23_06.yaml:6952:9
    [34m |
    [39m[34m6952 |[39m get:
    [34m |
    [39m
    [33mwarning[39m: OpenAPI host must be present and non-empty string.
    [34m–>[39m feb24_01.yaml:1:1
    [34m |
    [39m[34m1 |[39m —
    [34m |
    [39m
    [33mwarning[39m: OpenAPI host schemes must be present and non-empty array.
    [34m–>[39m feb24_01.yaml:1:1
    [34m |
    [39m[34m1 |[39m —
    [34m |
    [39m
    [33mwarning[39m: root spec object should declare a parameters property
    [34m–>[39m feb24_01.yaml:1:1
    [34m |
    [39m[34m1 |[39m —
    [34m |
    [39m
    [33mwarning[39m: root object should declare a responses property
    [34m–>[39m feb24_01.yaml:1:1
    [34m |
    [39m[34m1 |[39m —
    [34m |
    [39m
    [33mwarning[39m: root object should declare a definitions property
    [34m–>[39m feb24_01.yaml:1:1
    [34m |
    [39m[34m1 |[39m —
    [34m |
    [39m
    [33mwarning[39m: root spec object should declare a securityDefinitions object
    [34m–>[39m feb24_01.yaml:1:1
    [34m |
    [39m[34m1 |[39m —
    [34m |
    [39m
    [33mwarning[39m: Info object should contain contact object.
    [34m–>[39m feb24_01.yaml:3:6
    [34m |
    [39m[34m3 |[39m info:
    [34m |
    [39m

    • Brady GasterMicrosoft employee 0

      I’m unfamiliar with that CLI. It looks like every possible warning is being thrown during validation of the OpenAPI spec you’ve generated/authored. Does your API explicitly call out which content-type you expect?

  • Christopher Edwards 0

    So I’m completely new to OpenAPI and swagger etc. so I though I’d give this a try, starting with getting the controllers decorated for Swashbuckle and then marvelling at the SwaggerUI with a view to eventually getting generated clients.

    It took a bit of time to get SwaggerUI working. Swashbuckle could do with some guidance as to what has gone wrong! I excluded all my controllers from the project and started adding them back one by one and it was all looking fairly promising.

    The issue I have is that I use overloaded methods, think

    [HttpGet("{ProductID:int}")]
    public async Task GetProductByID(int ProductID)
    {
    ...
    }
    
    [HttpGet("{ProductName}")]
    public async Task GetProductByName(string ProductName)
    {
    ...
    }

    This blows it up. It seems that either Swashbuckle or even OpenAPI itself only supports a one Verb/Path combination.

    https://swagger.io/docs/specification/paths-and-operations/

    Shame.

  • Mohammad Shaddad 0

    Thanks Brady for the look at the tooling. this is a more of a code first approach to an API dev where you start with the code first and use tools to generate the OpenAPi spec for you at build/runtime. I try to discourage this pattern in my teams and encourage a contract first API where they first write the OpenAPI spec first, and then use that to generate the server/client code from that.
    One of the things that we have been exploring, is using the build generated OpenAPI spec as a test artifact and comparing it to the pre-defined contract to decide on whether the build is good to go or not.

Feedback usabilla icon