How to generate a WebAPI client for your ASP.NET Core 8.0 WebAPI: A Step-by-Step Guide with Swashbuckle and NSwag

ASP.NET Core WebAPIs are a great way to expose your business logic to the world. But how do you consume them? In this article, I’ll show you how to generate a WebAPI client for your ASP.NET Core WebAPI with Swashbuckle and NSwag.

WebAPI

Introduction

A good API is a well-documented API. But even with the best documentation, it can be hard to consume an API. Developers have to write a lot of boilerplate code to consume an API, including making HTTP requests, serializing and deserializing JSON, and handling errors. This can be time-consuming and error-prone.

It is important to provide a great developer experience. One way to do this is to provide a client library that developers can use to consume your API. ASP.NET Core WebAPI template already provides a way to show the API documentation using Swashbuckle. But it doesn’t provide a way to generate a client library. In this article, I’ll show you how to generate a WebAPI client for your ASP.NET Core WebAPI with Swashbuckle and NSwag, so that you can provide the client library to your consumers.

Swashbuckle is a library that automatically generates API documentation from your ASP.NET Core WebAPI. The default template for ASP.NET Core WebAPI includes Swashbuckle.

NSwag is similar to Swashbuckle. It does not only generate API documentation but also can generate client code for your API. It supports C# and TypeScript.

Note that NSwag provides a GUI application named NSwagStudio to generate the client library. However, it is not suitable for the CI/CD pipeline. In this article, we won’t use NSwagStudio. Instead, we’ll use the Swashbuckle CLI tool and the NSwag CLI tool to generate the client library in the build process, so that you can integrate it into your CI/CD pipeline.

Prerequisites

Before we start, make sure you have the following installed:

You can download the source code for this article from GitHub.

Creating an ASP.NET Core WebAPI

First, let’s create an ASP.NET Core WebAPI. Use VS 2022 to create a new project. Choose ASP.NET Core Web API template and click Create. Check the Enable OpenAPI support option to include Swashbuckle. Name the project WebApiClientDemo.

The default WeatherForecast controller is good enough for our demo.

Check the Program.cs file, and you should see the following code:

1
2
3
4
5
6
7
builder.Services.AddSwaggerGen();
// Omitted
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

Run the application and navigate to https://localhost:<your port number>/swagger/index.html. You should see the Swagger UI showing the API documentation.

Configuring the OpenAPI specification

We can add more information to the OpenAPI specification. So, developers can have a better understanding of the API. We can add the title, description, and contact information to the OpenAPI specification. We can also include the XML comments from the controllers in the OpenAPI document.

  1. Update the AddSwaggerGen() method in the Program.cs file as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    builder.Services.AddSwaggerGen(options =>
    {
    options.SwaggerDoc("v1", new OpenApiInfo
    {
    Version = "v1",
    Title = "My Web API",
    Description = "An ASP.NET Core Web API",
    Contact = new OpenApiContact
    {
    Name = "My Team",
    Url = new Uri("https://myteam.com")
    }
    });
    });

    Feel free to update the OpenApiInfo object with your own information.

  2. By default, the OpenAPI specification doesn’t contain the comments from the controllers. You can add the following code to the WebApiDemo.csproj file to include the comments from the controllers in the OpenAPI document:

    1
    2
    3
    4
    <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);1591</NoWarn>
    </PropertyGroup>

    This code tells the compiler to generate an XML documentation file and to suppress the warning 1591, which is the warning for missing XML comments (CS1591: Missing XML comment for publicly visible type or member).

  3. Then, add the following code to the AddSwaggerGen() method:

    1
    2
    var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));

    The preceding code tells Swashbuckle to include the XML comments in the OpenAPI document. So that you can see the comments in the Swagger UI. Add some comments to the WeatherForecast controller and the action methods.

So far, we have configured Swashbuckle to generate the OpenAPI specification with more information and include the XML comments from the controllers. When you run the application and navigate to https://localhost:<your_port_number>/swagger/index.html, you should see the updated Swagger UI with more information.

SwaggerUI

You can see the title, description, and contact information in the Swagger UI. You can also see a link such as https://localhost:<your_port_number>/swagger/v1/swagger.json. This is the URL to the OpenAPI specification.

Generating an OpenAPI specification file with Swashbuckle

Although we can see the OpenAPI specification in the Swagger UI, we need to generate a JSON file for NSwag to consume. Either Swashbuckle or NSwag can generate the OpenAPI specification file. However, Swashbuckle supports header parameters, while NSwag has some limitations. So, we’ll use Swashbuckle to generate the OpenAPI specification file, and then use NSwag to generate the client code.

  1. Swashbuckkle provides a CLI tool to generate the OpenAPI specification file. You can use the following command to install the Swashbuckle CLI tool:

    1
    dotnet tool install -g Swashbuckle.AspNetCore.Cli
  2. Then, you can run the following command to generate the OpenAPI specification file:

    1
    swagger tofile --output swagger-api.json bin\Debug\net8.0\WebApiClientDemo.dll v1

    The preceding command has the following parameters:

    • The swagger tofile command is used to generate the OpenAPI specification file.
    • The --output option is used to specify the output file.
    • The bin\Debug\net8.0\WebApiClientDemo.dll is the relative path to the Web API project.
    • The v1 option is the name of the OpenAPI document, which is defined in the AddSwaggerGen() method.

    If the command is successful, you should see the swagger-api.json file in the project folder.

If you get an error like Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Swashbuckle.AspNetCore.Swagger, Version=...', check the version of the Swashbuckle.AspNetCore package in the project. Make sure the version of the Swashbuckle CLI tool matches the version of the Swashbuckle.AspNetCore package. For example, in the sample project, the version of the Swashbuckle.AspNetCore package is 6.5.0, so the version of the Swashbuckle CLI tool should be 6.5.0. You can use the following command to install a specific version of the Swashbuckle CLI tool:

1
dotnet tool install -g Swashbuckle.AspNetCore.Cli --version 6.5.0

Also, please make sure the path to the assembly is correct.

Of course, we don’t want to run the Swashbuckle CLI tool manually every time we update the WebAPI. We can add a post-build event to the WebAPI project to generate the OpenAPI specification file automatically.

  1. To use the Swashbuckle CLI tool in the build process, run the following command to add a dotnet-tools.json file to the project:

    1
    dotnet new tool-manifest
  2. Then, you can run the following code to add the Swashbuckle CLI tool to the project:

    1
    dotnet tool install Swashbuckle.AspNetCore.Cli
  3. The preceding command will update the dotnet-tools.json file and add the Swashbuckle CLI tool to the project. You can find the dotnet-tools.json file in the project folder, as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "version": 1,
    "isRoot": true,
    "tools": {
    "swashbuckle.aspnetcore.cli": {
    "version": "6.5.0",
    "commands": [
    "swagger"
    ]
    }
    }
    }
  4. Next, update the .csproj file to include the swagger tofile command in the build process. Add the following code to the .csproj file:

    1
    2
    3
    4
    <PropertyGroup>
    <SolutionDirectory Condition=" '$(SolutionDirectory)' == '' ">$(MSBuildThisFileDirectory)..\</SolutionDirectory>
    <ApiAssembly>$(SolutionDirectory)WebApiClientDemo\bin\$(Configuration)\net8.0\WebApiClientDemo.dll</ApiAssembly>
    </PropertyGroup>

    The preceding code defines the SolutionDirectory and ApiAssembly properties. The ApiAssembly property points to the Web API project. We will use it later. Please update the path to the Web API project if it is different from the sample project.

  5. Then, add the following code to the .csproj file:

    1
    2
    3
    4
    <Target Name="BuildSwaggerFile" AfterTargets="Build">
    <Exec EnvironmentVariables="ASPNETCORE_ENVIRONMENT=Development" Command="dotnet tool restore" />
    <Exec EnvironmentVariables="ASPNETCORE_ENVIRONMENT=Development" Command="dotnet tool run swagger tofile --output $(ProjectDir)openapi.json $(ApiAssembly) v1" />
    </Target>

    The preceding code defines the BuildSwaggerFile target, which is executed after the Build target. There are two Exec tasks in the BuildSwaggerFile target:

    • The first Exec task restores the Swashbuckle CLI tool. It is useful when you use the pipeline to build the project.
    • The second Exec task runs the swagger tofile command to generate the OpenAPI specification file. The ApiAssembly property is used to specify the Web API project.

Right-click the project and select Rebuild. If the build is successful, you should see the openapi.json file in the project folder.

Creating a WebAPI client project

Next, let’s create a WebAPI client project. We can publish the client library to NuGet or distribute it as a package later.

  1. Create a new project in the same solution. Choose Class Library template and click Next. Name the project WebApiClientDemo.Client.

  2. Delete the Class1.cs file in the WebApiClientDemo.Client project.

  3. Add the reference to the WebApiClientDemo project, so, when we build the client project, the WebAPI project will be built first to ensure the OpenAPI specification file is up to date.

  4. Add the following code to the WebApiClientDemo.Client.csproj file:

    1
    2
    3
    4
    5
    6
    <PropertyGroup>
    <PackageId>WebApiClientDemo.Client</PackageId>
    <RepositoryUrl>https://github.com/yanxiaodi/MyCodeSamples</RepositoryUrl>
    <VersionPrefix>0.0.1</VersionPrefix>
    <PackageTags>WebApiClient,Client</PackageTags>
    </PropertyGroup>

    The preceding code defines the PackageId, RepositoryUrl, VersionPrefix, and PackageTags properties. These properties are used to generate the NuGet package. You can find more information about the properties in the NuGet documentation. Update the properties with your own information.

Generating a WebAPI client with NSwag

Now that we have the OpenAPI specification file and a client project. Next, let’s use NSwag to generate the WebAPI client.

  1. Create an interface named IWebApiClient in the WebApiClientDemo.Client project. Add the following code to the IWebApiClient.cs file:

    1
    2
    3
    4
    namespace WebApiClientDemo.Client;
    public interface IWebApiClient
    {
    }

    This interface will be used to define the REST API client library. All client classes will implement this interface.

  2. Install the NSwag.MSBuild package in the WebApiClientDemo.Client project. The NSwag.MSBuild package provides the MSBuild tasks to generate the REST API client library. Run the following command to install the package:

    1
    dotnet add package NSwag.MSBuild
  3. Create a nswag.json file in the MyWebApiDemo.WebApiClient project and add the following code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    {
    "runtime": "Net80",
    "documentGenerator": {
    "fromDocument": {
    "url": "../WebApiClientDemo/openapi.json",
    "output": null,
    "newLineBehavior": "Auto"
    }
    },
    "codeGenerators": {
    "openApiToCSharpClient": {
    "generateClientClasses": true,
    "generateClientInterfaces": true,
    "clientBaseInterface": "IWebApiClient",
    "generateDtoTypes": true,
    "injectHttpClient": false,
    "disposeHttpClient": true,
    "generateExceptionClasses": true,
    "exceptionClass": "SwaggerException",
    "wrapDtoExceptions": true,
    "useHttpClientCreationMethod": false,
    "httpClientType": "System.Net.Http.HttpClient",
    "useHttpRequestMessageCreationMethod": false,
    "useBaseUrl": true,
    "generateBaseUrlProperty": true,
    "generateSyncMethods": false,
    "exposeJsonSerializerSettings": false,
    "clientClassAccessModifier": "public",
    "typeAccessModifier": "public",
    "generateContractsOutput": false,
    "parameterDateTimeFormat": "s",
    "generateUpdateJsonSerializerSettingsMethod": true,
    "serializeTypeInformation": false,
    "queryNullValue": "",
    "className": "{controller}Client",
    "operationGenerationMode": "MultipleClientsFromOperationId",
    "generateOptionalParameters": false,
    "generateJsonMethods": true,
    "parameterArrayType": "System.Collections.Generic.IEnumerable",
    "parameterDictionaryType": "System.Collections.Generic.IDictionary",
    "responseArrayType": "System.Collections.ObjectModel.ObservableCollection",
    "responseDictionaryType": "System.Collections.Generic.Dictionary",
    "wrapResponses": false,
    "generateResponseClasses": true,
    "responseClass": "SwaggerResponse",
    "namespace": "WebApiClientDemo.Client",
    "requiredPropertiesMustBeDefined": true,
    "dateType": "System.DateTime",
    "dateTimeType": "System.DateTime",
    "timeType": "System.TimeSpan",
    "timeSpanType": "System.TimeSpan",
    "arrayType": "System.Collections.ObjectModel.ObservableCollection",
    "dictionaryType": "System.Collections.Generic.Dictionary",
    "arrayBaseType": "System.Collections.ObjectModel.ObservableCollection",
    "dictionaryBaseType": "System.Collections.Generic.Dictionary",
    "classStyle": "Inpc",
    "generateDefaultValues": true,
    "generateDataAnnotations": true,
    "excludedTypeNames": [],
    "handleReferences": false,
    "generateImmutableArrayProperties": false,
    "generateImmutableDictionaryProperties": false,
    "output": "WebApiClient.cs"
    }
    }
    }

    The preceding code defines the nswag.json file, which is used to generate the REST API client library. There are some important properties in the nswag.json file:

    • runtime: The target runtime for the client library.
    • documentGenerator: The document generator configuration. The fromDocument property specifies the url property of the OpenAPI specification. It can use a relative path to the OpenAPI specification file.
    • generateClientClasses: Whether to generate the client classes.
    • generateClientInterfaces: Whether to generate the client interfaces.
    • clientBaseInterface: The base interface for the client classes. In this case, we use the IWebApiClient interface.
    • className: The class name for the client classes. The {controller} placeholder is used to specify the controller name.
    • namespace: The namespace for the client classes.
    • output: The output file for the client library.

    Update the nswag.json file with your own information. To learn more about the nswag.json file, you can refer to the NSwag configuration documentation.

  4. Add the following code to the WebApiClientDemo.Client.csproj file:

    1
    2
    3
    <Target Name="NSwag" AfterTargets="Build">
    <Exec WorkingDirectory="$(ProjectDir)" EnvironmentVariables="ASPNETCORE_ENVIRONMENT=Development" Command="$(NSwagExe_Net80) run /input:nswag.json" />
    </Target>

    The preceding code executes the nswag.json file to generate the REST API client library. The command is nswag run /input:nswag.json. To learn more about the NSwag commands, you can refer to the NSwag command-line documentation.

  5. Right-click the WebApiClientDemo.Client project and select Rebuild. If the build is successful, you should see the WebApiClient.cs file in the WebApiClientDemo.Client project. This file contains the REST API client library.

Use System.Text.Json instead of Newtonsoft.Json

By default, NSwag uses Newtonsoft.Json to serialize and deserialize JSON data. If you want to use System.Text.Json instead of Newtonsoft.Json, you can add the following code to the nswag.json file:

1
2
3
4
5
6
7
8
9
{
...
"codeGenerators": {
"openApiToCSharpClient": {
"jsonLibrary": "SystemTextJson",
"jsonSerializerSettingsTransformationMethod": null,
}
}
}

The jsonLibrary property is used to specify the JSON library. The jsonSerializerSettingsTransformationMethod property is used to specify the method to transform the JSON serializer settings. If you don’t need to transform the JSON serializer settings, you can set it to null.

Customizing the method names

Check the generated WebApiClient.cs file, and you can find the following code:

1
System.Threading.Tasks.Task<System.Collections.ObjectModel.ObservableCollection<WeatherForecast>> GetWeatherForecastAsync();

How does NSwag know the method name is GetWeatherForecastAsync?

In the nswag.json file, you can find the following configuration:

1
2
3
4
5
{
...
"operationGenerationMode": "MultipleClientsFromOperationId",
...
}

The operationGenerationMode property is used to specify the operation generation mode. The MultipleClientsFromOperationId mode generates the client classes based on the operation ID. This means that the method names in the client classes are based on the operation ID. Check the OpenAPI specification file, and you can find the following code:

1
2
3
4
5
6
7
8
9
"/WeatherForecast": {
"get": {
"tags": [
"WeatherForecast"
],
"summary": "Get the weather forecast",
"operationId": "WeatherForecast"
}
}

The operationId property is used to specify the operation ID. In this case, the operation ID is WeatherForecast. Where is it from?

Open the WeatherForecastController.cs file, and you can find the following code:

1
2
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()

The Name property of the HttpGet attribute is used to specify the operation ID. So, the method name in the client class is GetWeatherForecastAsync.

In the sample project, you can find a controller named ProductsController. The actions in this controller don’t have the Name property. So what will happen?

If we don’t specify the Name property for the action methods, the OpenAPI specification file will not contain the operationId property. NSwag will use the HTTP methods instead. So you will see the following methods:

1
2
3
4
System.Threading.Tasks.Task<string> ProductsGETAsync(int id);
System.Threading.Tasks.Task ProductsPOSTAsync(string body);
System.Threading.Tasks.Task ProductsPUTAsync(int id, string body);
System.Threading.Tasks.Task ProductsDELETEAsync(int id);

This looks a little bit weird. To solve this issue, you can add the Name property to the action methods to specify the operation ID. However, if you have many action methods, it can be tedious. You can configure the operation IDs for action methods in the Program.cs file. Add the following code to the AddSwaggerGen() method:

1
options.CustomOperationIds(e => $"{e.ActionDescriptor.RouteValues["controller"]}_{e.ActionDescriptor.RouteValues["action"]}");

The preceding will use the controller name and action name as the operation ID. Generate the OpenAPI specification file again, and you will see the endpoints now have the operation IDs, such as Products_GetProduct, Products_CreateProduct, Products_UpdateProduct, and Products_DeleteProduct, etc. The generated client classes will have the method names based on the operation IDs:

1
2
3
4
System.Threading.Tasks.Task<string> GetProductAsync(int id);
System.Threading.Tasks.Task CreateProductAsync(string body);
System.Threading.Tasks.Task UpdateProductAsync(int id, string body);
System.Threading.Tasks.Task DeleteProductAsync(int id);

Now it looks better.

Providing an extension method to register the REST API client library

Next, we can provide an extension method to register the REST API client library in other projects.

  1. Create a WebApiClientOptions class to store the base URL of the REST API in the WebApiClientDemo.Client project. Add the following code to the WebApiClientOptions.cs file:

    1
    2
    3
    4
    5
    namespace WebApiClientDemo.Client;
    public class WebApiClientOptions
    {
    public string? BaseUrl { get; set; }
    }

    You can add more configurations if you need.

  2. Create a WebApiClientExtensions.cs class and add the following code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    using Microsoft.Extensions.DependencyInjection;

    namespace WebApiClientDemo.Client;
    public static class WebApiClientExtensions
    {
    private const string BaseUrlNullOrEmptyErrorMessage = "Base Url can't be null or empty.";
    public static IServiceCollection AddWebApiClients(this IServiceCollection services, WebApiClientOptions options)
    {
    if (services == null)
    {
    throw new ArgumentNullException(nameof(services));
    }

    if (options == null || string.IsNullOrWhiteSpace(options.BaseUrl))
    {
    throw new ArgumentNullException(nameof(options), BaseUrlNullOrEmptyErrorMessage);
    }

    services.AddHttpClient<IWeatherForecastClient, WeatherForecastClient>(client =>
    {
    client.BaseAddress = new Uri(options.BaseUrl);
    });
    services.AddHttpClient<IProductsClient, ProductsClient>(client =>
    {
    client.BaseAddress = new Uri(options.BaseUrl);
    });

    return services;
    }
    }

    The preceding code provides an extension method to register the REST API client library. If your web API has more controllers, you need to register them in the AddWebApiClients() method.

Next, you can publish this project as a NuGet package and share it with other teams. As the API client code is generated in the build process, you can easily update the client library when the WebAPI is updated in your CI/CD pipeline. The other teams can install the package and register the API client library using the extension method in their projects. This can save a lot of time and reduce the chance of errors.

If you want to publish this project as a NuGet package, you can refer to the Publishing a package documentation, or check my article Using Azure Pipelines to publish the NuGet package from GitHub repo.

Summary

In this article, I showed you how to generate a WebAPI client for your ASP.NET Core WebAPI with Swashbuckle and NSwag. We used Swashbuckle to generate the OpenAPI specification file and NSwag to generate the REST API client library. We also provided an extension method to register the REST API client library in other projects. NSwag provides a GUI tool to generate the client library, but it is not suitable for the CI/CD pipeline. By using the Swashbuckle CLI tool and the NSwag CLI tool, we can generate the client library in the build process of the CI/CD pipelines. This can speed up the development process and reduce the chance of errors.

Further Reading