This article explains how to generate a Emply API v2 client in .NET/C#.
This examples uses NSwag with Newtonsoft.JSON as JSON library.
Note
For other languages, you can use the tools listed here: OpenAPI.Tools - an Open Source list of great tools for OpenAPI.
You can also generate an API client using an AI agent. An example of a prompt:
Create a .NET console application that has an API client, generated using NSwag (NewtonsoftJson as JsonLibrary), that connects to the following endpoint: https://api.emply.com/v2/swagger/swagger.json
-
Install NSwag.
dotnet tool install nswag.consolecore
-
Generate an API client using Nswag-
nswag openapi2csclient /input:https://api.emply.com/v2/swagger/swagger.json /classname:EmplyApiClient /namespace:Emply /output:Emply/EmplyApiClient.cs /GenerateClientInterfaces:true /GenerateExceptionClasses:true /ExceptionClass:EmplyApiException /UseHttpClientCreationMethod:true /JsonLibrary:NewtonsoftJson /DateTimeType:System.DateTime
-
Install
Newtonsoft.JSONNuGet package.dotnet add package Newtonsoft.Json
-
Currently there’s a bug in NSwag that prevents some of the API endpoints to be invoked correctly. For that reason a workaround should be applied.
-
Create a C# file “
NswagPolymorphicSerializationFix.cs" with the following content.using System.Collections.Concurrent; using System.Reflection; using Newtonsoft.Json.Serialization; using Newtonsoft.Json; namespace Emply { public static class NswagPolymorphicSerializationFix { /// <summary> /// Fixes a polymorphic serialization bug in code generated by NSwag: https://github.com/RicoSuter/NSwag/issues/3217 /// </summary> public static CustomResolver GetPatchedContractResolver(this Type apiClientType) { Assembly asm = apiClientType.Assembly; string @namespace = apiClientType.Namespace ?? string.Empty; IEnumerable<(Type type, string discriminatorField)> polymorphicTypes = from type in asm.GetExportedTypes() where type.IsClass && type.Namespace == @namespace let jsonConverter = type.GetCustomAttribute<JsonConverterAttribute>(false) where jsonConverter != null && jsonConverter.ConverterType == typeof(JsonInheritanceConverter) let discriminatorField = jsonConverter.ConverterParameters?.OfType<string>().FirstOrDefault() where discriminatorField != null select (type, discriminatorField); Dictionary<Type, JsonConverter> converters = polymorphicTypes.ToDictionary( t => t.type, t => (JsonConverter) Activator.CreateInstance(typeof(PatchedJsonInheritanceConverter<>).MakeGenericType(t.type), t.discriminatorField)); return new CustomResolver(converters); } public class CustomResolver : DefaultContractResolver { private readonly Dictionary<Type, JsonConverter> _converters; public CustomResolver(Dictionary<Type, JsonConverter> converters) { _converters = converters; } protected override JsonObjectContract CreateObjectContract(Type objectType) { JsonObjectContract contract = base.CreateObjectContract(objectType); Type? baseType = objectType.BaseType; if (baseType != null && _converters.TryGetValue(baseType, out JsonConverter converter)) { contract.Converter = converter; } return contract; } } public class PatchedJsonInheritanceConverter<TBaseType> : JsonInheritanceConverter { [System.ThreadStatic] private static bool _isWritingWrapper; private static readonly ConcurrentDictionary<Type, string> _subtypeDiscriminatorCache = new(); public PatchedJsonInheritanceConverter(string discriminatorName) : base(discriminatorName) { } public override bool CanConvert(Type objectType) { return objectType.IsClass && objectType.IsSubclassOf(typeof(TBaseType)); } public override bool CanWrite => !_isWritingWrapper && base.CanWrite; public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { try { _isWritingWrapper = true; Newtonsoft.Json.Linq.JObject jObject = Newtonsoft.Json.Linq.JObject.FromObject(value, serializer); jObject.Remove(DiscriminatorName); // Remove existing discriminator if present jObject.AddFirst(new Newtonsoft.Json.Linq.JProperty(DiscriminatorName, GetSubtypeDiscriminator(value.GetType()))); writer.WriteToken(jObject.CreateReader()); } finally { _isWritingWrapper = false; } } private string GetSubtypeDiscriminator(Type objectType) { return _subtypeDiscriminatorCache.GetOrAdd(objectType, type => { IEnumerable<JsonInheritanceAttribute> attributes = type.GetTypeInfo().GetCustomAttributes<JsonInheritanceAttribute>(true); foreach (JsonInheritanceAttribute attribute in attributes) { if (attribute.Type == type) { return attribute.Key; } } return type.Name; }); } } } } -
Create a C# file
EmplyApiClient.fix.csextendingEmplyApiClient.using Newtonsoft.Json; namespace Emply; public partial class EmplyApiClient { static partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) { settings.ContractResolver = typeof(EmplyApiClient).GetPatchedContractResolver(); } }
-
-
Create an instance of
EmplyApiClientwith an access token provider via theAuthorizationheader to call the API.Example 8. Example
using Emply; string baseUrl = "https://api.emply.com"; string bearerToken = "eyJhbGc...."; // Generate a key via Emply WebApp var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}"); var emplyClient = new EmplyApiClient(baseUrl, httpClient); var timeZones = await emplyClient.GetTimeZonesAsync(); Console.WriteLine("TimeZones fetched from Emply API:"); Console.WriteLine("----------------------------------"); foreach (var timeZone in timeZones) { Console.WriteLine($"{timeZone.Title}: {timeZone.Offset}"); }Output