Stop writing clients in C# for your Web APIs
Image from Unsplash

Stop writing clients in C# for your Web APIs

Auto generating client libraries and packages for Web API using NSwag and Azure DevOps

Times of writing your HttpClient setup to call REST API endpoint are over. It is just to much of repetitive code and taking care of the endpoints of the REST API service. I remember the days when SOAP was popular. Visual Studio would generate C# proxy classes for you based on the WSDL service definition and you would not have to care about the endpoint or the actual HttpClient class instance used for invoking the service endpoints. It was kind-a weird not have something like this for REST and have to write everything from the scratch for every REST service. Well there is option for that and there are in fact few of them. 

In this article I will cover usage of NSwag for generating clients for REST API in C# and TypeScript. Since I an not that much into TypeScript I will focus more on C#, but I will also walk through generating TypeScript clients.

Build WebAPI clients with PowerShell script

To start with NSwag you need to make sure you have documented your REST API with Swagger. I will not go deep with Swagger in this document, but you can check out some articles I wrote related to Swagger usage for documenting ASP.NET Core Web API projects (Setting up Swagger to support versioned API endpoints in ASP.NET CoreDisplaying Azure DevOps build number in Swagger UI for ASP.NET CoreDisplaying Azure DevOps build number in Swagger UI for ASP.NET Core) or you can check Microsoft Documentation for a kick start.

Let's assume you have your API documented with Swagger, we can start using NSwag. First thing you need to is add NuGet package references to Nswag.AspNetCore and NSwag.MSBuild packages in your API project.

<Project Sdk="Microsoft.NET.Sdk.Web">
...
  <ItemGroup>
  ...
    <!-- Client code generator -->
    <PackageReference Include="NSwag.AspNetCore" Version="12.2.3" />
    <PackageReference Include="NSwag.MSBuild" Version="12.2.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    <Product>Sample API Service</Product>
    <Description>Sample ASP.NET Core WebApi REST Service</Description>
    </PackageReference>
  </ItemGroup>
  ...
</Project>

    
    

One you build and publish your API project these two packages will add libraries to publish folder and you will be able to call them to generate C# and TypeScript client classes. To make things simple, I will use NSwag libraries just to generate clients and put them in a Zip archive and copy to wwwroot folder of your Web API so that they can be downloaded and used in either C# or TypeScript (Angular, React, Vue) clients.

Note

I am working on Windows 10 machine, so I'm using PowerShell to generate client from the command line. If you are on Mac on Linux, you can use this PowerShell script to write your bash. The logic is the same, just command would be different in bash syntax

Set-Location $env:USERPROFILE\.nuget\packages\nswag.msbuild\12.2.3\tools\NetCore22

$projectName = "MySample.Api"
$binFolder = "D:\Repos\MySample.Api\src\"+ $projectName +"\bin\Debug\netcoreapp2.2\publish"
$outFolder = $binFolder + "\wwwroot"
$versions = @("v1","v2")
foreach ($version in $versions) {

Write-Host "GENERATING CLIENTS FOR"$version
dotnet dotnet-nswag.dll aspnetcore2swagger /assembly:$binFolder\$projectName.dll /documentName:$version /output:$outFolder\$projectName.$version.json
dotnet dotnet-nswag.dll swagger2csclient /output:$outFolder\$projectName.Clients.$version.Client.cs /namespace:$projectName.$version /InjectHttpClient:true /input:$outFolder\$projectName.$version.json
dotnet dotnet-nswag.dll swagger2tsclient /output:$outFolder\$projectName.Clients.$version.Client.ts /namespace:$projectName.$version /input:$outFolder\$projectName.$version.json

Write-Host "GENERATING ARCHIVE FOR"$version
Compress-Archive -LiteralPath $outFolder\$projectName.Clients.$version.Client.cs, $outFolder\$projectName.Clients.$version.Client.ts, $outFolder\$projectName.$version.json -DestinationPath $outFolder\$projectName.Clients.$version.zip -Force

Write-Host "REMOVING VERSION"$version" FILES"
Remove-Item –path $outFolder\$projectName.Clients.$version.Client.cs
Remove-Item –path $outFolder\$projectName.Clients.$version.Client.ts 
Remove-Item –path $outFolder\$projectName.$version.json

}
    

Unfortunately, I could not find the way to iterate throught the versions, so API versions array is manually set at the beginning of the loop. If you have nay idea how to access versions this with NSwag, feel free to reply to this article.

One parameter for NSwag command if particularly important to include and that is /InjectHttpClient:true. This parameter build a client class that will allow you to send pre-configured instance of HttpClient class to the generated client class. This is especially usefull for the authentication. Your API consumer application will most likely have to first authenticate with authentication/authorization service first and than just supply the token in headers on the call. This particular parameter will tell NSwag to generate class constructor that takes HttpClient class instance in the constructor.

You can start using generated C# client right away by adding it to your client API consumer project, but this is not really the most elegant way to reference client library. Every time you make even the smallest change in the public API methods, you would have to generate client, take the client class, copy it to the project and then use it in the code. Sounds like a lot of manual work, for the simple reason that it is a lot of manual work :) and since this is 2019, does not sound like something you would have in your software life-cycle. Let's see how can we automate this.

Using Azure DevOps build pipeline to generate NuGet packages for client libraries

OK, we know how to generate our client libraries and use them to consume WEB API, but we need to automate the part where we compile those clients and put them in a common accessible place for other projects that might use our API. NSwag can generate clients in both C# and TypeScript, but I am going to focus on the C# auto generated clients and how to use and manage them.

The most logical way for a .NET project to have commonly used library is a NuGet package, so let's see how can we use the NSwag generates .cs class file to generate NuGet package and push it to the NuGet repository. First thing first, we are going to need a project where we are going to add our NSwag generated classes.

In our existing solution we need to add new .NET Standard library project. Since .NET Core and .NET Standard projects are explicitly excluding files, it means that as soon as we add a .cs file to the project folder, it will be included in the build unlike .NET Framework 1.x - 4.x projects where we need to explicitly include file in the project to be considered for the build. This makes things a lot easier for our case.

Solution Explorer

If you open auto generated C# client from a script run from above, you can see that there are few package references code relies on. For that reason we need to bring in those references to our client library .NET Standard project

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Description>Autogenerated API client</Description>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
    <PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
  </ItemGroup>

</Project>

    

Now let's switch to Azure DevOps and create a build pipeline. Azure DevOps gives you good startup by creating a default ASP.NET Core build pipeline with restore/build/publish steps.

Pipeline Default Core

We need to add client generate script that will invoke NSwag in order to generate api client classes, copy the result to our empty client library project, build it and ship it to our own NuGet package feed. Before we start updating the default pipeline, make sure you have your NuGet feed create in Azure DevOps. 

Nuget Feed

We will be referring to this feed though out the whole build process. There are several benefits to use Azure DevOps NuGet package feed compared to the public NuGet.org package repository:

  • Azure Artifacts feed allows the build agent to retrieve packages faster as they are available from the same network
  • You can control access to your package repository and enable it only for your organization or individuals who work n the project
  • Azure Artifacts NuGet feed caches packages from the public NuGet repository, so in case of unavailability of the public repository, build agent will still get the packages from the feed cache

Once we build and publish our API project in the pipeline we can generate clients and copy them to clients project library for further building, publishing and pushing to NuGet feed. I use PowerShell for all of this with Azure Hosted Windows 2019 with VS2019 for a build agent. Since PowerShell core with same PowerShell syntax runs fine on both Linux and Mac, it should also run on these type of agents, but to be honest, I haven't tried. As I mention, I use Windows for my development, so stick with Windows for building though for hosting my application it does not matter as .NET Core runs on anything.

Set-Location $env:USERPROFILE\.nuget\packages\nswag.msbuild\12.2.3\tools\NetCore22

$projectName = "MySample.Api"
$binFolder = "$(build.artifactstagingdirectory)" + "\" + $projectName
$outFolder = $binFolder + "\wwwroot"
$versions = @("v1","v2")
foreach ($version in $versions) {

Write-Host "GENERATING CLIENTS FOR"$version
dotnet dotnet-nswag.dll aspnetcore2swagger /assembly:$binFolder\$projectName.dll /documentName:$version /output:$outFolder\$projectName.$version.json
dotnet dotnet-nswag.dll swagger2csclient /output:$outFolder\$projectName.Clients.$version.Client.cs /namespace:$projectName.Clients.$version /InjectHttpClient:true /input:$outFolder\$projectName.$version.json
dotnet dotnet-nswag.dll swagger2tsclient /output:$outFolder\$projectName.Clients.$version.Client.ts /namespace:$projectName.Clients.$version /input:$outFolder\$projectName.$version.json

Write-Host "GENERATING ARCHIVE FOR"$version
Compress-Archive -LiteralPath $outFolder\$projectName.Clients.$version.Client.cs, $outFolder\$projectName.Clients.$version.Client.ts, $outFolder\$projectName.$version.json -DestinationPath $outFolder\$projectName.Clients.$version.zip -Force

Write-Host "COPY TO NUGET PACKAGE PROJECT FOLDER"
Copy-Item "$outFolder\$projectName.Clients.$version.Client.cs" -Destination "$(Build.SourcesDirectory)\src\$projectName.Clients"

Write-Host "REMOVING VERSION"$version" FILES"
Remove-Item –path $outFolder\$projectName.Clients.$version.Client.cs
Remove-Item –path $outFolder\$projectName.Clients.$version.Client.ts 
Remove-Item –path $outFolder\$projectName.$version.json

}
    

You can see that there is not many constants in the code since I am following convention to have my <API project name>.Clients.csproj for my clients library. Not something you should strictly follow, but conventions in general allow you to follow certain pattern and reduce number of lines and coding effort, so it is up to you.

I left the part of pushing the clients Zip archive to wwwroot, but you can omit that since your packages will be available from your NuGet feed. The following are the tasks in a pipeline as an addition to clients project build and NuGet package push to a private artifacts feed.

Pipeline Nswag Nuget

I renamed all the tasks with an addition of project name which the command is executed on, so you can easily see what is going on on which project.

Note

NuGet task from the pallet of tasks for the build pipeline cannot be used for .NET Core projects. Instead you need to use .NET Core task and set command to pack/push for the tasks that build and publish NuGet package

Your build pipeline is now ready to build your API along with the client nuget package. Now all that is left is to reference your generated NuGet packages in the API consumer projects. You have the full explanation how to reference your NuGet feed in Azure DevOps feed options in Artifacts section.

Update - .NET Core CLI

In the sample above I used this empty .NET Standard class library project to be used as a container for a new clients library assembly. It has no functional role in the whole solution except to be used during the build process for hosting NSwag generated client classes.

This works just fine, but it adds additional project to your solution which at some point may lead to a confusion to persons who work on the solution. Especially if you by any chance have more than one API project in a solution and therefore you will have a project for each and every client library for every API. You can even from the sentence above conclude that things are getting messy. To avoid scenario like this you need to introduce some more logic into your pipeline and that logic would be creating clients projects on the fly during the build time in the Azure DevOps pipeline.

Note

This applies only to .NET Core projects. If you are still running on .NET Framework 4.x, you won't have ability to use .NET Core CLI to create your project on the fly during the build time

We do not need to change our pipeline, but instead we only need to extend the PowerShell task with some .NET Core CLI commands. Not to forget, if you are going to rely completely on the CLI, you are safe to delete the empty clients project from your solution. Let's see how the updated PowerShell task would look like now.

Set-Location $env:USERPROFILE\.nuget\packages\nswag.msbuild\12.2.3\tools\NetCore22
$projectName = "MySample.Api"
$binFolder = "$(build.artifactstagingdirectory)" + "\" + $projectName
$nugetSource = "https://pkgs.dev.azure.com/dejanstojanovic/_packaging/MySample/nuget/v3/index.json"
$outFolder = $binFolder + "\temp"
$versions = @("v1")
New-Item -ItemType "directory" -Path "$outFolder" -Force
foreach ($version in $versions) {
Write-Host "GENERATING CLIENTS FOR"$version
dotnet dotnet-nswag.dll aspnetcore2swagger /assembly:$binFolder\$projectName.dll /documentName:$version /output:$outFolder\$projectName.$version.json
dotnet dotnet-nswag.dll swagger2csclient /output:$outFolder\$projectName.Clients.$version.Client.cs /namespace:$projectName.Clients.$version /InjectHttpClient:true /input:$outFolder\$projectName.$version.json
dotnet dotnet-nswag.dll swagger2tsclient /output:$outFolder\$projectName.Clients.$version.Client.ts /namespace:$projectName.Clients.$version /input:$outFolder\$projectName.$version.json
Write-Host "CREATE PACKAGE PROJECT FOLDER"
dotnet new classlib --name=$projectName.Clients --framework=netstandard2.0 --output="$(Build.SourcesDirectory)\src\$projectName.Clients"
Set-Location $(Build.SourcesDirectory)\src\$projectName.Clients
del $(Build.SourcesDirectory)\src\$projectName.Clients\Class1.cs
dotnet add package Newtonsoft.Json --version="12.0.2" --source="$nugetSource"
dotnet add package System.ComponentModel.Annotations --version="4.5.0" --source="$nugetSource"
dotnet restore

Write-Host "COPY TO NUGET PACKAGE PROJECT FOLDER"
Copy-Item "$outFolder\$projectName.Clients.$version.Client.cs" -Destination "$(Build.SourcesDirectory)\src\$projectName.Clients"

Write-Host "REMOVING VERSION"$version" FILES"
Remove-Item -Recurse -Force $outFolder


}
    

Now we fully automated our build process and we will be getting packages pushed to our NuGet feed every time the WebApi project is build. We no longer rely on the solution structure and there is no additional setup like empty container projects for the clients as a part of our solution which reduces the potential break points in our automated CI/CD process.

As an addition, the script also uses the same NuGet feed for retrieving the external NuGet packages hosted on nuget.org. This decreases the restore time and most importantly it reduces your build pipeline dependency on the external resources as you Azure NuGHet feed cashes all packages pulled from the nuget.org.

After the build process if you got to your Azure Artifacts NuGet feed, you will see that apart from your created NuGet package, there are also all additional packages used by all projects that were build in the pipeline.

Nuget Feed (1)

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article