Auto Generated .NET API Clients using NSwag and Swashbuckle Swagger

This article shows how auto generated code for a C# HTTP API client could be created using Swagger and NSwag . The API was created using ASP.NET Core MVC.

Code https://github.com/damienbod/csvSwaggerExample

Create the API using ASP.NET Core and Swashbuckle Swagger

The API is created using ASP.NET Core with Swashbuckle. Add the required Nuget packages to the project, set the GenerateDocumentationFile element to true and also add the NoWarn element, if all the C# code is not documented.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);1591</NoWarn>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="4.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="4.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="4.0.1" />
    <PackageReference Include="WebApiContrib.Core.Formatter.Csv" Version="3.0.0" />
  </ItemGroup>

</Project>

In the Startup class, add the Swagger configuration in the ConfigureServices method. The AddSwaggerGen extension method uses the XML file for the comments.

public void ConfigureServices(IServiceCollection services)
{
	services.AddSwaggerGen(c =>
	{
		c.SwaggerDoc("v1", new Info
		{
			Version = "v1",
			Title = "CSV TEST API",
		});

		// comments path
		var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
		var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
		c.IncludeXmlComments(xmlPath);
	});

Use the Swagger middleware to create the UI and the Json file with the API documentation. The UI part is not required for NSwag.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	app.UseStaticFiles();
	app.UseMvc();

	app.UseSwagger();
	app.UseSwaggerUI(c =>
	{
		c.SwaggerEndpoint("/swagger/v1/swagger.json", "CSV Test API V1");
	});
}

Add an API as required. Here is a basic example of a CRUD REST API with definitions, which will be picked up by the Swagger documentation.

using System.Collections.Generic;
using System.Linq;
using System.Net;
using CsvWebApiSwagger.Models;
using Microsoft.AspNetCore.Mvc;

namespace CsvWebApiSwagger.Controllers
{
    /// <summary>
    /// Jobs API for CRUD job 
    /// </summary>
    [Route("api/[controller]")]
    public class JobsController : Controller
    {
        /// <summary>
        /// Gets all jobs using the API
        /// </summary>
        /// <returns>Return a list of jobs</returns>
        [HttpGet]
        [ProducesResponseType(typeof(IEnumerable<Job>), (int)HttpStatusCode.OK)]
        public IActionResult Get()
        {
            return Ok(Jobs);
        }

        /// <summary>
        /// get Job using the id
        /// </summary>
        /// <param name="id">job id</param>
        /// <returns>Job for the ID</returns>
        [HttpGet("{id}")]
        [ProducesResponseType(typeof(IEnumerable<Job>), (int)HttpStatusCode.OK)]
        public IActionResult Get(int id)
        {
            if (id == 0)
            {
                return BadRequest();
            }

            var jobExists = Jobs.Exists(j => j.Id == id);

            if (jobExists == false)
            {
                return NotFound($"Job with Id not found: {id}");
            }

            return Ok(Jobs.First(j => j.Id == id));
        }

        /// <summary>
        /// Creates a new JOB if the ID does not already exist
        /// </summary>
        /// <remarks>
        /// Sample create Job:
        ///
        ///     POST api/Jobs
        ///     {
        ///        "id": int id which does not exist,
        ///        "title": "title of the job",
        ///        "description": "Description of the job",
        ///        "level": "level of the job",
        ///        "requirements": "Requirements of the job",
        ///     }
        ///
        /// </remarks>
        /// <param name="job">Job to create</param>
        /// <returns>The created JOB</returns>
        [HttpPost]
        [ProducesResponseType(typeof(Job), (int)HttpStatusCode.OK)]
        [ProducesResponseType(typeof(Job), (int)HttpStatusCode.Conflict)]
        public IActionResult Post([FromBody]Job job)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest();
            }

            var jobAlreadyExists = Jobs.Exists(j => j.Id == job.Id);

            if (jobAlreadyExists == true)
            {
                return Conflict($"Job with Id {job.Id} exists");
            }

            Jobs.Add(job);

            return Ok(job);
        }

        /// <summary>
        /// put a string
        /// </summary>
        /// <param name="id">id of a string</param>
        /// <param name="value">value of the string</param>
        [HttpPut("{id}")]
        [ProducesResponseType(typeof(Job), (int)HttpStatusCode.OK)]
        public IActionResult Put(int id, [FromBody]Job value)
        {
            if (id == 0)
            {
                return BadRequest();
            }

            if (!ModelState.IsValid)
            {
                return BadRequest();
            }

            var job = Jobs.First(j => j.Id == id);
            job.Description = value.Description;
            job.Level = value.Level;
            job.Title = value.Title;
            job.Requirements = value.Requirements;

            return Ok(value);
        }

        /// <summary>
        /// delete the Job if it is found
        /// </summary>
        /// <param name="id">job id</param>
        [HttpDelete("{id}")]
        [ProducesResponseType((int)HttpStatusCode.OK)]
        [ProducesResponseType((int)HttpStatusCode.NotFound)]
        [ProducesResponseType((int)HttpStatusCode.Conflict)]
        public IActionResult Delete(int id)
        {
            if (id == 0)
            {
                return BadRequest();
            }

            var job = Jobs.First(j => j.Id == id);

            if (job == null)
            {
                return Conflict($"Job with Id {id} does not exist");
            }

            Jobs.Remove(job);
            return Ok();
        }
    }
}

Creating the API client using NSwag

NSwag can be used to create a C# class, which implements the client for the API. This can be created using the NSwagStudio created by Rico Suter.

Download this, install it and open it. Then configure the tool, to read from the API. (Start the API first). Set the namespace to the same as the target project, and save to class where it is required.

This generated class can then be used in any application, and for a Console .NET Core application, only the Json Nuget package is required.

Here is a simple example of the API usage.

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace ConsoleApiClient
{
    class Program
    {
        public static void Main(string[] args) => MainAsync().GetAwaiter().GetResult();

        static async Task MainAsync()
        {
            Console.WriteLine("begin...");

            HttpClient httpClient = new HttpClient();

            var clientCsvWebApiSwagger = new ClientCsvWebApiSwagger(
                "https://localhost:44354/", httpClient);

            var all = await clientCsvWebApiSwagger.GetAllAsync();

            Console.WriteLine($"amount of jobs: {all.Count}");

            Console.WriteLine($"Create job: Id = 2340");

            var job = await clientCsvWebApiSwagger.PostAsync(new Job
            {
                Id = 2340,
                Description = "created using the NSwag generated code",
                Level = "amazing",
                Requirements = "Json nugetg package",
                Title = "NSwag generate"
            });

            all = await clientCsvWebApiSwagger.GetAllAsync();

            Console.WriteLine($"amount of jobs: {all.Count}");
            Console.ReadLine();

        }
    }
}

The NSwag configuration can be saved and commited to the project for reuse later.

{
  "runtime": "NetCore22",
  "defaultVariables": null,
  "swaggerGenerator": {
    "fromSwagger": {
      "json": "{\r\n  \"swagger\": \"2.0\",\r\n  \"info\": {\r\...}",
      "url": "https://localhost:44354/swagger/v1/swagger.json",
      "output": null
    }
  },
  "codeGenerators": {
    "swaggerToCSharpClient": {
      "clientBaseClass": null,
      "configurationClass": null,
      "generateClientClasses": true,
      "generateClientInterfaces": false,
      "generateDtoTypes": true,
      "injectHttpClient": true,
      "disposeHttpClient": true,
      "protectedMethods": [],
      "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,
      "contractsNamespace": null,
      "contractsOutputFilePath": null,
      "parameterDateTimeFormat": "s",
      "generateUpdateJsonSerializerSettingsMethod": true,
      "serializeTypeInformation": false,
      "queryNullValue": "",
      "className": "{controller}ClientCsvWebApiSwagger",
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "additionalNamespaceUsages": [],
      "additionalContractNamespaceUsages": [],
      "generateOptionalParameters": false,
      "generateJsonMethods": true,
      "enforceFlagEnums": false,
      "parameterArrayType": "System.Collections.Generic.IEnumerable",
      "parameterDictionaryType": "System.Collections.Generic.IDictionary",
      "responseArrayType": "System.Collections.Generic.ICollection",
      "responseDictionaryType": "System.Collections.Generic.IDictionary",
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "namespace": "ConsoleApiClient",
      "requiredPropertiesMustBeDefined": true,
      "dateType": "System.DateTimeOffset",
      "jsonConverters": null,
      "dateTimeType": "System.DateTimeOffset",
      "timeType": "System.TimeSpan",
      "timeSpanType": "System.TimeSpan",
      "arrayType": "System.Collections.Generic.ICollection",
      "arrayInstanceType": "System.Collections.ObjectModel.Collection",
      "dictionaryType": "System.Collections.Generic.IDictionary",
      "dictionaryInstanceType": "System.Collections.Generic.Dictionary",
      "arrayBaseType": "System.Collections.ObjectModel.Collection",
      "dictionaryBaseType": "System.Collections.Generic.Dictionary",
      "classStyle": "Poco",
      "generateDefaultValues": true,
      "generateDataAnnotations": true,
      "excludedTypeNames": [],
      "handleReferences": false,
      "generateImmutableArrayProperties": false,
      "generateImmutableDictionaryProperties": false,
      "jsonSerializerSettingsTransformationMethod": null,
      "inlineNamedDictionaries": false,
      "inlineNamedTuples": true,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "ConsoleApiClient/MyApiClient.cs"
    }
  }
}

When the applications are started, the API can be used and no client code, models need to be implemented manually.

Links

https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag

https://github.com/RSuter/NSwag

https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle

https://github.com/RSuter/NSwag/wiki/NSwagStudio

https://swagger.io/

https://github.com/dmitry-pavlov/openapi-connected-service

8 comments

    1. Cool, thanks for the link

  1. OpenAPI (Swagger) Connected Service – a Visual Studio 2017 extension to generate C# HttpClient code for OpenAPI (formerly Swagger API) web service with NSwag.

    https://marketplace.visualstudio.com/items?itemName=dmitry-pavlov.OpenAPIConnectedService

    1. Thanks Dmitry

  2. […] on February 12, 2019by admin submitted by /u/MaximRouiller [link] [comments] No comments […]

  3. […] !Auto Generated .NET API Clients using NSwag and Swashbuckle Swagger (2019-02) […]

  4. The “Unchase OpenAPI (Swagger) Connected Service” is a Visual Studio 2017/2019 extension to generate C# (TypeScript) HttpClient (or C# Controllers) code for OpenAPI (formerly Swagger) web services with NSwag with customization of code generation like in NSwagStudio:

    https://marketplace.visualstudio.com/items?itemName=Unchase.unchaseopenapiconnectedservice

    See How-To in medium.com: https://medium.com/@unchase/how-to-generate-c-or-typescript-client-code-for-openapi-swagger-specification-d882d59e3b77

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: