Generating API clients using AutoRest

Posted

This blog post looks at automatically generating an HTTP API client from a OpenAPI specification (formerly swagger) using AutoRest.

We will also use another tool called Swashbuckle which is an ASP.NET Core library that allows us to annotate our controllers with metadata that is used to generate OpenAPI specifications.

Installing AutoRest

The first thing we need to do is to install AutoRest globally on our machine. If you have NPM installed on your computer, then this can be done by running the following command:

> npm install -g autorest

Setting up the code base

For this project we're going to create a standard ASP.NET Core web project, a class library that will hold our API client and a PowerShell script to generate the client for us.

Why PowerShell and not Cake you might wonder if you know about my involvement in the Cake project, and the reason is simple; I want this guide to be as build agnostic as it can be, but don't worry, I will follow up with a detailed guide of how to convert the PowerShell script to a fully fledged Cake build script.

Start with creating the project.

> mkdir autorest-example
> cd autorest-example

Now we want to create a source directory where the source code for our API project will live.

> mkdir src
> cd src

We continue by creating a solution file, and the two projects I mention above using the dotnet CLI.

> dotnet new sln --name AutoRestExample
> dotnet new webapi --name Sample.Api
> dotnet new classlib --name Sample.Client
> dotnet sln AutoRestExample.sln add ./Sample.Api/Sample.Api.csproj
> dotnet sln AutoRestExample.sln add ./Sample.Client/Sample.Client.csproj

After this is done you can open the solution file in your editor of choice. For this tutorial/guide I will be using Visual Studio, but nothing stops you from using Visual Studio Code, Rider or a similar IDE/editor.

Modifying the API project

We're going to use a library called Swashbuckle to generate the OpenAPI specification directly from our ASP.NET Core controllers, so we will need to install the necessary NuGet packages.

We will also need the Swashbuckle.AspNetCore.Cli tool installed as part of our project. This is not a package reference but a project tool, so we will need to add the tool to our csproj manually. Right click on the Sample.Api project in the solution explorer and paste the following snippet somewhere under the <Project> node.

<ItemGroup>
  <DotNetCliToolReference Include="Swashbuckle.AspNetCore.Cli" Version="3.0.0-beta1" />
</ItemGroup>

The controller

Open up Controllers/ValuesController.cs and replace the content with the following code:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;

namespace Sample.Api.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        [ProducesResponseType(typeof(ICollection<string>), 200)]
        [SwaggerOperation(OperationId = "Values_GetAll")]
        public ActionResult<ICollection<string>> Get()
        {
            return new string[] { "foo", "bar" };
        }
    }
}

As you can see we have a method that returns a collection of string. We've decorated the method with two additional attributes that tells Swashbuckle that this method will return a collection of strings if the HTTP response code is 200. We're also setting an Swagger operation ID for the method that will be used when we generate our client.

Wiring up the API

We now have a controller that's decorated with the necessary attributes for Swashbuckle to generate a Swagger specification from it. We now have to wire everything up in the Startup.cs file.

In the ConfigureServices method, add the following code that will tell Swashbuckle that we've annotated our controllers with annotations, and provide some metadata for the API.

services.AddSwaggerGen(c =>
{
    c.EnableAnnotations();
    c.SwaggerDoc("v1", new Info
    {
        Title = "Sample API",
        Version = "v1"
    });
});

We now have to add the Swashbuckle middleware to our application pipeline which we do by adding the following code last in the Configure method. You can also (optionally) add the UI, but this is not required for what we're trying to achieve.

app.UseSwagger();

// Optional
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample API");
});

If you run the project and navigate to /swagger/v1/swagger.json, you will see the generated Swagger specification that represents our API.

Modifying the client project

The last thing we need to do before actually generating a client is to add a reference to the Microsoft.Rest.ClientRuntime NuGet package in the Sample.Client project. This package contains everything that a generated AutoRest client requires to work.

Generating the client code

Now we want to start generating the client code, but we don't want to get the Swagger document by starting the API and retrieving it. It's absolutely possible but will be hard to automate.

It's here the Swashbuckle.AspNetCore.Cli package comes into play.
What this package does is to allow to generate the Swagger document from a built assembly instead of invoking the Swagger endpoint of our API project.

What we need to do now is:

  1. Build the Sample.API project.
  2. Generate the Swagger definition from the built assembly.
  3. Generate the client from the Swagger definition using AutoRest.

Like I mentioned before, we're going to use PowerShell to do this, so go ahead and create a Generate.ps1 file in the root of our project structure. The file should look like following:

Set-Location src

# Build project.
dotnet build

# Create the Swagger file from the API assembly.
Set-Location Sample.Api
dotnet swagger "tofile" --output "../../res/swagger.json" "../Sample.Api/bin/Debug/netcoreapp2.1/Sample.Api.dll" v1
Set-Location ..

# Clear the previously generated code.
Remove-Item "Sample.Client/Generated" -Force -Recurse

# Generate a C# client from the Swagger definition and
# override the namespace and the client name.
autorest --input-file="../res/swagger.json" `
         --output-folder="Sample.Client/Generated" `
         --namespace="Sample.Client" `
         --override-client-name="SampleClient" `
         --csharp

# Reset the location.
Set-Location ..

By running this script you should find a folder called Generated in the Client project containing the API client.

All classes are partial (as well as some methods) so if you need to inject your own logic or change some behavior (such as authentication), you have the possibility to do so.

Closing up

This tutorial just goes through the basics of how to generate an API client, but there is a myriad of other options that you can use to customize the generated client to your exact needs. I've personally haven't encountered a single scenario that couldn't be solved by one of the extensibility points in the generated code.

I also recommend you to always commit the generated Swagger file as part of your repository. That way you always have a way to quickly regenerate the client if you manage to break something.

You can find the source code to this blog post at https://github.com/patriksvensson/autorest-example