I created an enum on my server with integer values set manually rather than the default increment up from 0
public enum UserType
{
Anonymous = 0,
Customer = 10,
Technician = 21,
Manager = 25,
Primary = 30
}
My server is running using AspNetCore.App 2.2.0. It's configured in Startup.cs with swashbuckle aspnetcore 4.0.1 to generate a swagger json file to describe the api every time the server is started.
I then use NSwag Studio for windows v 13.2.3.0 to generate a C sharp api client with that swagger JSON file, for use in a Xamarin app. The generated enum in the resulting c sharp api client looks like this - the underlying integer values do not match the original enum.
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.5.0 (Newtonsoft.Json v11.0.0.0)")]
public enum UserType
{
[System.Runtime.Serialization.EnumMember(Value = @"Anonymous")]
Anonymous = 0,
[System.Runtime.Serialization.EnumMember(Value = @"Customer")]
Customer = 1,
[System.Runtime.Serialization.EnumMember(Value = @"Technician")]
Technician = 2,
[System.Runtime.Serialization.EnumMember(Value = @"Manager")]
Manager = 3,
[System.Runtime.Serialization.EnumMember(Value = @"Primary")]
Primary = 4,
}
This creates a problem for me client side as there are situations where I need to know the integer value. I am looking for a solution where I can avoid writing converters every time I want to know the integer value on the client side.
Option 1: Is there an option I am missing in either NSwag Studio or in .net configuration (my Startup.Cs config is below for reference) where I can force the generated enums to get the same integer values as the original enum?
Option 2: Alternatively if not, both my client and my server have access to the same original enum via a shared class library. Is there a way to get the generated api client to use the actual original enums in the apiclient.cs rather than generate its own?
Reference:
The enums part of my swagger generation code in Startup.Cs looks like this
services.AddJsonOptions(options =>
{
options.
SerializerSettings.Converters.Add(new StringEnumConverter());
....
services.AddSwaggerGen(setup =>
{
setup.SwaggerDoc("v1", new Info { Title = AppConst.SwaggerTitle, Version = "v1" });
setup.UseReferencedDefinitionsForEnums();
... other stuff...
}
So these are the two Enum Helpers I'm using. One is used by NSwag (x-enumNames
) and the other is used by Azure AutoRest (x-ms-enums
)
Finally found the reference for EnumDocumentFilter
(https://stackoverflow.com/a/49941775/1910735)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace SwaggerDocsHelpers
{
/// <summary>
/// Add enum value descriptions to Swagger
/// https://stackoverflow.com/a/49941775/1910735
/// </summary>
public class EnumDocumentFilter : IDocumentFilter
{
/// <inheritdoc />
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
// add enum descriptions to result models
foreach (var schemaDictionaryItem in swaggerDoc.Definitions)
{
var schema = schemaDictionaryItem.Value;
foreach (var propertyDictionaryItem in schema.Properties)
{
var property = propertyDictionaryItem.Value;
var propertyEnums = property.Enum;
if (propertyEnums != null && propertyEnums.Count > 0)
{
property.Description += DescribeEnum(propertyEnums);
}
}
}
if (swaggerDoc.Paths.Count <= 0) return;
// add enum descriptions to input parameters
foreach (var pathItem in swaggerDoc.Paths.Values)
{
DescribeEnumParameters(pathItem.Parameters);
// head, patch, options, delete left out
var possibleParameterisedOperations = new List<Operation> { pathItem.Get, pathItem.Post, pathItem.Put };
possibleParameterisedOperations.FindAll(x => x != null)
.ForEach(x => DescribeEnumParameters(x.Parameters));
}
}
private static void DescribeEnumParameters(IList<IParameter> parameters)
{
if (parameters == null) return;
foreach (var param in parameters)
{
if (param is NonBodyParameter nbParam && nbParam.Enum?.Any() == true)
{
param.Description += DescribeEnum(nbParam.Enum);
}
else if (param.Extensions.ContainsKey("enum") && param.Extensions["enum"] is IList<object> paramEnums &&
paramEnums.Count > 0)
{
param.Description += DescribeEnum(paramEnums);
}
}
}
private static string DescribeEnum(IEnumerable<object> enums)
{
var enumDescriptions = new List<string>();
Type type = null;
foreach (var enumOption in enums)
{
if (type == null) type = enumOption.GetType();
enumDescriptions.Add($"{Convert.ChangeType(enumOption, type.GetEnumUnderlyingType())} = {Enum.GetName(type, enumOption)}");
}
return $"{Environment.NewLine}{string.Join(Environment.NewLine, enumDescriptions)}";
}
}
public class EnumFilter : ISchemaFilter
{
public void Apply(Schema model, SchemaFilterContext context)
{
if (model == null)
throw new ArgumentNullException("model");
if (context == null)
throw new ArgumentNullException("context");
if (context.SystemType.IsEnum)
{
var enumUnderlyingType = context.SystemType.GetEnumUnderlyingType();
model.Extensions.Add("x-ms-enum", new
{
name = context.SystemType.Name,
modelAsString = false,
values = context.SystemType
.GetEnumValues()
.Cast<object>()
.Distinct()
.Select(value =>
{
//var t = context.SystemType;
//var convereted = Convert.ChangeType(value, enumUnderlyingType);
//return new { value = convereted, name = value.ToString() };
return new { value = value, name = value.ToString() };
})
.ToArray()
});
}
}
}
/// <summary>
/// Adds extra schema details for an enum in the swagger.json i.e. x-enumNames (used by NSwag to generate Enums for C# client)
/// https://github.com/RicoSuter/NSwag/issues/1234
/// </summary>
public class NSwagEnumExtensionSchemaFilter : ISchemaFilter
{
public void Apply(Schema model, SchemaFilterContext context)
{
if (model == null)
throw new ArgumentNullException("model");
if (context == null)
throw new ArgumentNullException("context");
if (context.SystemType.IsEnum)
{
var names = Enum.GetNames(context.SystemType);
model.Extensions.Add("x-enumNames", names);
}
}
}
}
Then in your startup.cs you configure them
services.AddSwaggerGen(c =>
{
... the rest of your configuration
// REMOVE THIS to use Integers for Enums
// c.DescribeAllEnumsAsStrings();
// add enum generators based on whichever code generators you decide
c.SchemaFilter<NSwagEnumExtensionSchemaFilter>();
c.SchemaFilter<EnumFilter>();
});
This should generate your enums as this in the Swagger.json file
sensorType: {
format: "int32",
enum: [
0,
1,
2,
3
],
type: "integer",
x-enumNames: [
"NotSpecified",
"Temperature",
"Fuel",
"Axle"
],
x-ms-enum: {
name: "SensorTypesEnum",
modelAsString: false,
values: [{
value: 0,
name: "NotSpecified"
},
{
value: 1,
name: "Temperature"
},
{
value: 2,
name: "Fuel"
},
{
value: 3,
name: "Axle"
}
]
}
},
There is one issue with this solution though, (which I haven't had time to look into) Is that the Enum names are generated with my DTO names in NSwag - If you do find a solution to this do let me know :-)
Example, the following Enum was generated using NSwag:
Wow Dawood thanks so much - I copied your code into my solution, compiled it, and it worked first time! I'm really impressed. As for the names thing - one thing you can do is use setup.UseReferencedDefinitionsForEnums(); inside the AddSwaggerGen method. This will produce enums that are shared between the api calls and are named exactly as your original enums. On the client side this could cause a headache if it can see both the original enums and the api version, but if you refactor so that your client side entities reference the api generated enums directly you should be fine. Thanks again!
@AdamDiament did your enums generate in the swagger.json same as the server? I don't understand the refactor part - as my enums are used in DTOs?
The enums get generated in the swagger json as new enums but with the exact same names as the ones on the server. I have refactored my dtos / viewmodels client side to reference the new api enums instead of the server ones, and remove all references on the client to the namespaces where the server enums are defined. Then when the api client sends http calls using its own enums, they get deserialized as the server enums on the server without issue.