This article introduce how to use WebApiClientGen and Code First approach for generating client APIs for ASP.NET Core Web API, in C# and in TypeScript for jQuery, Aurelia, Axios and Angular 2+; and also compare WebApiClientGen with Swagger/OpenAPI toolchain for .NET and ASP.NET Core.
Introduction
For developing client programs of ASP.NET Web API or ASP. NET Core Web API, Strongly Typed Client API Generators generate strongly typed client API in C# codes and TypeScript codes. The toolkit is to minimize repetitive tasks, streamline the coordination between the backend development and the frontend development, and improve the productivity of the dev team and the quality of the product through less efforts, works and stress.
This open source project provides these products:
- Code generator for strongly typed client API in C# supporting desktop, Universal Windows, Android and iOS.
- Code generator for strongly typed client API in TypeScript for jQuery, Angular 2+ and Aurelia, as well as TypeScript/JavaScript applications that use Axios.
- TypeScript CodeDOM, a CodeDOM component for TypeScript, derived from CodeDOM of .NET Framework.
- POCO2TS.exe, a command line program that generates TypsScript interfaces from POCO classes.
- Fonlow.Poco2Ts, a component that generates TypsScript interfaces from POCO classes
This article is focused on generating C# Client API libraries for ASP.NET Core 2.0/3.0, while the full coverage is at "Generate C# Client API for ASP.NET Web API". For client API libraries in TypeScript, please check the other article.
Using the Code
The installation will also install dependent NuGet packages Fonlow.TypeScriptCodeDOMCore
and Fonlow.Poco2TsCore
to the project references.
Step 1: Post NuGet Installation
Step 1.1 Create CodeGenController
In your Web API project, add the following controller:
#if DEBUG //This controller is not needed in production release, since the client API should be generated during development of the Web Api.
using Fonlow.CodeDom.Web;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using System.Linq;
using System.Net;
namespace Fonlow.WebApiClientGen
{
[ApiExplorerSettings(IgnoreApi = true)]
[Route("api/[controller]")]
public class CodeGenController : ControllerBase
{
private readonly IApiDescriptionGroupCollectionProvider apiExplorer;
private readonly string webRootPath;
public CodeGenController(IApiDescriptionGroupCollectionProvider apiExplorer, IWebHostEnvironment hostingEnvironment)
{
this.apiExplorer = apiExplorer;
this.webRootPath = hostingEnvironment.WebRootPath;
}
[HttpPost]
public ActionResult TriggerCodeGen([FromBody] CodeGenSettings settings)
{
if (settings == null)
return BadRequest("No settings");
if (settings.ClientApiOutputs == null)
return BadRequest("No settings/ClientApiOutputs");
Fonlow.Web.Meta.WebApiDescription[] apiDescriptions;
try
{
var descriptions = ApiExplorerHelper.GetApiDescriptions(apiExplorer);
apiDescriptions = descriptions.Select(d => Fonlow.Web.Meta.MetaTransform.GetWebApiDescription(d)).OrderBy(d => d.ActionDescriptor.ActionName).ToArray();
}
catch (System.InvalidOperationException e)
{
System.Diagnostics.Trace.TraceWarning(e.Message);
return StatusCode((int)HttpStatusCode.InternalServerError, e.Message);
}
if (!settings.ClientApiOutputs.CamelCase.HasValue)
{
settings.ClientApiOutputs.CamelCase = true;
}
try
{
CodeGen.GenerateClientAPIs(this.webRootPath, settings, apiDescriptions);
}
catch (Fonlow.Web.Meta.CodeGenException e)
{
var msg = e.Message + " : " + e.Description;
System.Diagnostics.Trace.TraceError(msg);
return BadRequest(msg);
}
return Ok("Done");
}
}
}
#endif

Remarks
The CodeGenController
should be available only during development in the debug build, since the client API should be generated only once for each version of the Web API.
Step 1.2 Make ApiExplorer Become Visible
In Startup.cs, add the highlighted line below:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(
options =>
{
#if DEBUG
options.Conventions.Add
(new Fonlow.CodeDom.Web.ApiExplorerVisibilityEnabledConvention());
#endif
}
);
Using ApiExplorerVisibilityEnabledConvention
is an opt-out approach to include all controllers except those decorated by ApiExplorerSettingsAttribute
or ApiControllerAttribute.
Alternatively, if you prefer opt-in approach, you may use ApiExplorerSettingsAttribute
to decorate a Web API controller, like this one:
[ApiExplorerSettings(IgnoreApi = false)]
[Route("api/[controller]")]
public class HeroesController : ControllerBase
{
Then there's no need to add ApiExplorerVisibilityEnabledConvention
.
Step 2: Create .NET Framework or .NET Core Client API Project

Step 3: Prepare JSON Config Data
Your Web API project may have POCO classes and API functions like the ones below:
namespace DemoWebApi.DemoData
{
public sealed class Constants
{
public const string DataNamespace = "http://fonlow.com/DemoData/2014/02";
}
[DataContract(Namespace = Constants.DataNamespace)]
public enum AddressType
{
[EnumMember]
Postal,
[EnumMember]
Residential,
};
[DataContract(Namespace = Constants.DataNamespace)]
public enum Days
{
[EnumMember]
Sat = 1,
[EnumMember]
Sun,
[EnumMember]
Mon,
[EnumMember]
Tue,
[EnumMember]
Wed,
[EnumMember]
Thu,
[EnumMember]
Fri
};
...
[DataContract(Namespace = Constants.DataNamespace)]
public class Entity
{
public Entity()
{
Addresses = new List<Address>();
}
[DataMember]
public Guid Id { get; set; }
[DataMember(IsRequired =true)]
[System.ComponentModel.DataAnnotations.Required]
public string Name { get; set; }
[DataMember]
public IList<Address> Addresses { get; set; }
public override string ToString()
{
return Name;
}
}
[DataContract(Namespace = Constants.DataNamespace)]
public class Person : Entity
{
[DataMember]
public string Surname { get; set; }
[DataMember]
public string GivenName { get; set; }
[DataMember]
public DateTime? BirthDate { get; set; }
public override string ToString()
{
return Surname + ", " + GivenName;
}
}
...
namespace DemoWebApi.Controllers
{
[Route("api/[controller]")]
public class EntitiesController : Controller
{
[HttpGet]
[Route("getPerson/{id}")]
public Person GetPerson(long id)
{
return new Person()
{
Surname = "Huang",
GivenName = "Z",
Name = "Z Huang",
DOB = DateTime.Now.AddYears(-20),
};
}
[HttpPost]
[Route("createPerson")]
public long CreatePerson([FromBody] Person p)
{
Debug.WriteLine("CreatePerson: " + p.Name);
if (p.Name == "Exception")
throw new InvalidOperationException("It is exception");
Debug.WriteLine("Create " + p);
return 1000;
}
[HttpPut]
[Route("updatePerson")]
public void UpdatePerson([FromBody] Person person)
{
Debug.WriteLine("Update " + person);
}
The JSON config data is like this:
{
"ApiSelections": {
"ExcludedControllerNames": [
"DemoWebApi.Controllers.Home",
"DemoWebApi.Controllers.FileUpload"
],
"DataModelAssemblyNames": [
"DemoWebApi.DemoDataCore",
"DemoCoreWeb"
],
"CherryPickingMethods": 3
},
"ClientApiOutputs": {
"ClientLibraryProjectFolderName": "..\\..\\..\\..\\..\\DemoCoreWeb.ClientApi",
"GenerateBothAsyncAndSync": true,
"StringAsString": true,
"CamelCase": true,
"Plugins": [
{
"AssemblyName": "Fonlow.WebApiClientGenCore.NG2",
"TargetDir": "..\\..\\..\\..\\..\\DemoNGCli\\NGSource\\src\\ClientApi",
"TSFile": "WebApiCoreNG2ClientAuto.ts",
"AsModule": true,
"ContentType": "application/json;charset=UTF-8"
}
]
}
}
It is recommended to save the JSON payload into a file as illustrated in this screenshot:

Hints
The ExcludedControllerNames
property will exclude those controllers that are already visible to ApiExplorer
, alternatively controllers decorated by [ApiExplorerSettings(IgnoreApi = true)]
won't be visible to ApiExplorer
.
StringAsString
is an option for .NET Core Web API which will return text/plain string
by default, rather than application/json JSON object, so the client codes generated won't deserialize the response body of respective Web API function.
Step 4: Run the DEBUG Build of the Web API Project and POST JSON Config Data to Trigger the Generation of Client API Codes
During development, you have 2 ways of launching the Web API within the VS solution folder.
DotNet
In command prompt, CD to a folder like C:\VSProjects\MySln\DemoCoreWeb\bin\Debug\netcoreapp3.0, then run
dotnet democoreweb.dll
or just run democoreweb.exe.
IIS Express
Run the Web project in the VS IDE, IIS Express will be launched to host the Web app.
Remarks
Different hostings of the Web app may result in different Web root path, so you may need to adjust the JSON config data accordingly for the folders.
You may create and run a PowerShell file to launch the Web service and POST:
cd $PSScriptRoot
<
Make sure CodeGen.json is saved in format ANSI or UTF-8 without BOM,
since ASP.NET Core 2.0 Web API will fail to deserialize POST Body that contains BOM.
$path = "$PSScriptRoot\DemoCoreWeb\bin\Debug\netcoreapp3.0"
$procArgs = @{
FilePath = "dotnet.exe"
ArgumentList = "$path\DemoCoreWeb.dll"
WorkingDirectory = $path
PassThru = $true
}
$process = Start-Process @procArgs
$restArgs = @{
Uri = 'http://localhost:5000/api/codegen'
Method = 'Post'
InFile = "$PSScriptRoot\DemoCoreWeb\CodeGen.json"
ContentType = 'application/json'
}
Invoke-RestMethod @restArgs
Stop-Process $process
Publish Client API Libraries
After these steps, now you have the client API in C# generated to a file named as WebApiClientAuto.cs, similar to this example:
public partial class Entities
{
private System.Net.Http.HttpClient client;
private System.Uri baseUri;
public Entities(System.Net.Http.HttpClient client, System.Uri baseUri)
{
if (client == null)
throw new ArgumentNullException("client", "Null HttpClient.");
if (baseUri == null)
throw new ArgumentNullException("baseUri", "Null baseUri");
this.client = client;
this.baseUri = baseUri;
}
public async Task<DemoWebApi.DemoData.Client.Person> GetPersonAsync(long id)
{
var requestUri = new Uri(this.baseUri, "api/Entities/getPerson/"+id);
var responseMessage = await client.GetAsync(requestUri);
responseMessage.EnsureSuccessStatusCode();
var stream = await responseMessage.Content.ReadAsStreamAsync();
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<DemoWebApi.DemoData.Client.Person>(jsonReader);
}
}
public DemoWebApi.DemoData.Client.Person GetPerson(long id)
{
var requestUri = new Uri(this.baseUri, "api/Entities/getPerson/"+id);
var responseMessage = this.client.GetAsync(requestUri).Result;
responseMessage.EnsureSuccessStatusCode();
var stream = responseMessage.Content.ReadAsStreamAsync().Result;
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<DemoWebApi.DemoData.Client.Person>(jsonReader);
}
}
public async Task<long> CreatePersonAsync(DemoWebApi.DemoData.Client.Person p)
{
var requestUri = new Uri(this.baseUri, "api/Entities/createPerson");
using (var requestWriter = new System.IO.StringWriter())
{
var requestSerializer = JsonSerializer.Create();
requestSerializer.Serialize(requestWriter, p);
var content = new StringContent(requestWriter.ToString(),
System.Text.Encoding.UTF8, "application/json");
var responseMessage = await client.PostAsync(requestUri, content);
responseMessage.EnsureSuccessStatusCode();
var stream = await responseMessage.Content.ReadAsStreamAsync();
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
var serializer = new JsonSerializer();
return System.Int64.Parse(jsonReader.ReadAsString());
}
}
}
Points of Interests
Controller and ApiController of ASP.NET, and Controller and ControllerBase of ASP.NET Core
In the old days before ASP.NET Web API, programmers had to use a MVC controller to create JSON-based Web API. Then Microsoft had created ASP.NET Web API, so programmers have been using System.Web.Http.ApiController
ever since. Now with ASP.NET Core, programmers use Microsoft.AspNetCore.Mvc.ControllerBase
or Microsoft.AspNetCore.Mvc.Controller
for creating Web APIs, while ControllerBase
supports Web API only and Controller
supports both Web API and MVC view.
Nevertheless, it may be wise not to mix API functions and View
functions in the same Controller
derived class.
Handling String in the HTTP Response
In ASP.NET Web API, if a Web API function returns a string
, the response body is always a JSON object, unless you provide a custom made formatter that returns string
as string
. In .NET Core Web API, such API function will by default return a string as a string in the response body, unless the client HTTP request provides an accept header "application/json
". When providing "StringAsString" : true
in the CodeGen JSON config, the client codes generated won't deserialize the response body of respective Web API function, and obviously this is more efficient if the Web API function will return a large string
.
About NuGet for .NET Core
Presumably, you have read "Generate C# Client API for ASP.NET Web API". When importing NuGet package Fonlow.WebApiClientGen, installing the NuGet package could copy CodeGenController
and other files to the Web project. However, for .NET Core Web project, Fonlow.WebApiClientGenCore could copy only the assemblies. Rick Strahl has explained well at:
.NET SDK Projects - No more Content and Tools
WebApiClientGen vs Swagger
Swagger here means Swashbuckle.AspNetCore on ASP.NET Core Web API plus NSwagStudio for generating client codes.
C# Clients
Swagger does not support:
- User defined struct.
- Object
- dynamic
Swagger does not give exact data type mappings for the following types:
- Decimal ==> double
- Nullable<T> ==> T
- float ==>double
- uint, short, byte ==> int
- ulong ==> long
- char==> string
- Tuple ==> Generated user defined type with similar structure to Tuple
- int[,] ==> ICollection<object>
- int[][] ==> ICollection<int>
- KeyValuePair ==> Generated user defined type with similar structure to KeyValuePair
Swagger generates verbose, larger and complex codes:
In the sln, Core3WebApi is with WebApiClientGen, and SwaggerDemo is with Swashbuckle.AspNetCore for creating an Open API definition. When generating async functions only, codes generated by WebApiClientGen is 97KB, along with debug build 166KB and release build 117KB, while Swagger's NSwagStudio gives 489KB-495KB, along with debug build 340KB-343KB and release build 263KB-283KB.
Swagger yield verbose GeneratedCodeAttribute
According to https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.generatedcodeattribute?view=netcore-3.1 , the GeneratedCodeAttribute class can be used by code analysis tools to identify computer-generated code, and to provide an analysis based on the tool and the version of the tool that generated the code.
It is a good practice to put generated codes into a dedicated assembly with generated codes only. Thus an application programmer may simply exclude the assembly from code analysis tools. Therefore GeneratedCodeAttribute is not necessary in the generated codes.
How WebApiClientGen is superior to Swagger?
For generating C# clients, WebApiClientGen supports more .NET built-in data types and give more exact data type mappings. Exact type mappings make client programming much easier for high quality since the integration tests should pick up data out of range easily because of proper type constraints.
Smaller codes and smaller compiled images are always welcome.
The manual steps of generating client codes is less and faster.
How Swagger is superior to WebApiClientGen?
Swagger here means the Open API standard and respective toolchains.
Swagger is an open standard and platform neutral, being supported by major software vendors and developed by hundreds of developers around the world. Microsoft Docs has a dedicated section for Swagger at https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-3.1, and Microsoft has been using Swagger for her own Web API products.
Swagger gives fine grained control over HTTP headers, while WebApiClientGen ignores this area.
Can WebApiClientGen and Swagger coexist?
Swagger here means Swashbuckle.AspNetCore on ASP.NET Core Web API plus NSwagStudio for generating client codes.
The answer is yes.
These two products are greatly overlapping in the .NET landscapes, while Swagger covers wider and deeper spectrum.
If you are developing ASP.NET (Core) Web API and expect all clients are coded in C# and TypeScript only, WebApiClientGen gives you more advantages.
When you need to support clients coded in languages other than C# and TypeScript, you may introduce Swagger into your Web API and generate the Open API definition files either in JSON or YAML.
Perfect SDLC with WebApiClientGen and Swagger
Whenever you as a backend developer has just updated the Web API, you run WebApiClientGen with a batch file and generate C# client codes and TypeScript client codes for other client developers. And the Swagger endpoint of the Web API gives the Open API definition files, so client developers working on other languages may generate client API codes in other languages.
So you get the best of WebApiClientGen and Swagger.
I started my IT career in programming on different embedded devices since 1992, such as credit card readers, smart card readers and Palm Pilot.
Since 2000, I have mostly been developing business applications on Windows platforms while also developing some tools for myself and developers around the world, so we developers could focus more on delivering business values rather than repetitive tasks of handling technical details.
Beside technical works, I enjoy reading literatures, playing balls, cooking and gardening.