Swagger C#枚举生成-基础int值与原始枚举不匹配
我在服务器上创建了一个枚举,并手动设置了整数值,而不是从0开始的默认增量Swagger C#枚举生成-基础int值与原始枚举不匹配,c#,asp.net-core,swagger,swagger-codegen,nswagstudio,C#,Asp.net Core,Swagger,Swagger Codegen,Nswagstudio,我在服务器上创建了一个枚举,并手动设置了整数值,而不是从0开始的默认增量 public enum UserType { Anonymous = 0, Customer = 10, Technician = 21, Manager = 25, Primary = 30 } 我的服务器正在使用AspNetCore.App 2.2.0运行。它在Startup.cs和Swashback aspnetcore 4.0.1中配置为在每次启动服务器时生成一个swagg
public enum UserType
{
Anonymous = 0,
Customer = 10,
Technician = 21,
Manager = 25,
Primary = 30
}
我的服务器正在使用AspNetCore.App 2.2.0运行。它在Startup.cs和Swashback aspnetcore 4.0.1中配置为在每次启动服务器时生成一个swagger json文件来描述api
然后,我使用NSwag Studio for windows v 13.2.3.0生成一个带有该Swigger JSON文件的C sharp api客户端,用于Xamarin应用程序。生成的c sharp api客户端中生成的枚举如下所示-基础整数值与原始枚举不匹配
[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,
}
这给我的客户端带来了一个问题,因为有些情况下我需要知道整数值。我正在寻找一种解决方案,在这种解决方案中,每当我想知道客户端的整数值时,我都可以避免编写转换器
备选案文1:
NSwag Studio或.net配置(我的Startup.Cs config在下面供参考)中是否缺少一个选项,我可以强制生成的枚举获得与原始枚举相同的整数值
备选案文2:
或者,如果不是,我的客户端和服务器都可以通过共享类库访问相同的原始枚举。有没有办法让生成的api客户端使用apiclient.cs中的实际原始枚举,而不是生成自己的枚举
参考:
Startup.Cs中我的招摇过市生成代码的枚举部分如下所示
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...
}
*更新* 在下面的答案中发布了一个有效的解决方案,它完全符合我的要求 *原始答案* 目前似乎没有办法做到这一点。正如@sellotape在他的评论中提到的,这甚至可能不是一个好主意。由于我控制着服务器,而且这是一个相对较新的项目,我已经将我的枚举重构为正常的“从零开始顺序”样式 我认为它对于一些用例是有用的,例如支持一个不易重构的遗留枚举,或者在中间,例如10,20,30,枚举枚举的能力。这将允许在以后插入11、12等,同时保留将某种“顺序”编码到枚举的能力,并且不会随着项目的增长而破坏该顺序
然而,目前看来这似乎不可能,因此我们将继续使用它。这是我正在使用的两个枚举帮助程序。一个用于NSwag(
x-enumNames
),另一个用于Azure AutoRest(x-ms-enums
)
终于找到了EnumDocumentFilter
()
不过,这个解决方案有一个问题(我还没有时间研究)
Enum名称是使用NSwag中的我的DTO名称生成的-如果您确实找到了解决方案,请告诉我:-)
例如,以下枚举是使用NSwag生成的:
如果您查看Swashback生成的swagger.json,您会发现,一旦您选择将枚举作为文本值发出,客户端将完全无法使用整数值(我认为这只是遵守OAS规范)。因此,NSwag无法知道文本后面的整数值。你可以使用整数代替,但是文本值有你可能不想失去的优势,在这种情况下,你必须考虑客户为什么需要两者,由于枚举的语义可能有点像那样被拉伸了。那么您是否改为使用枚举的整数值?否@DawoodAwan我保留了字符串值,因为它们比仅使用int更有用,但我将我的原始枚举重构为顺序整数0,1,2,3等,以确保它们与使用
int
值时需要了解客户端int值的用例相匹配,您可以创建一个招摇过市的助手来生成x-enum
值-在使用nSwag时生成整型值和字符串值-但是因为您使用了字符串值,所以我认为这是一个无效的答案。@DawoodAwan听起来很有希望-您可以发布一个代码示例吗?哇,Dawood非常感谢-我将您的代码复制到我的解决方案,编译后,第一次就成功了!我真的很感动。至于名称,您可以使用setup.userreferencedefinitionsforenums();在AddSwaggerGen方法中。这将生成api调用之间共享的枚举,并与原始枚举完全相同。在客户端,如果可以同时看到原始枚举和api版本,这可能会引起头痛,但是如果进行重构,以便客户端实体直接引用api生成的枚举,则应该可以。再次感谢@您的枚举在swagger.json中生成的AdamDiament与服务器相同吗?我不理解重构部分-因为我的枚举在DTO中使用?枚举在swagger json中作为新的枚举生成,但与服务器上的枚举名称完全相同。我已经重构了dtos/viewmodels客户端,以引用新的api枚举而不是服务器枚举,并删除客户端上对定义服务器枚举的命名空间的所有引用。然后,当api客户机使用自己的枚举发送http调用时,它们会被反序列化为服务器上的服务器枚举,而不会出现问题。
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);
}
}
}
}
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>();
});
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"
}
]
}
},