问题
Recently I upgraded my api from netcore2.1 to netcore3.1
I was hoping not to ask the api users to re-write their client.
Thus I expect I need the swagger.json to be backwardly compatible.
In the old swagger.json an enum would look like
But now it looks like
I am using Swashbuckle.AspNetCore 5.2.1
I have an extension called AddSwaggerDocumentation and it calls
services.AddSwaggerGen(c =>
{
c.EnableAnnotations();
c.ParameterFilter<SwaggerEnumParameterFilter>();
c.SchemaFilter<SwaggerEnumFilter>();
where
public class SwaggerEnumFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
var values = Enum.GetValues(context.Type);
var valuesArr = new OpenApiArray();
foreach (var value in values)
{
var item = new OpenApiObject
{
["name"] = new OpenApiString(Enum.GetName(context.Type, value)),
["value"] = new OpenApiString(value.ToString())
};
valuesArr.Add(item);
}
model.Extensions.Add("x-ms-enum", new OpenApiObject
{
["name"] = new OpenApiString(context.Type.Name),
["modelAsString"] = new OpenApiBoolean(true),
["values"] = valuesArr
});
}
}
}
also I have
public static IApplicationBuilder UseSwaggerDocumentation(this IApplicationBuilder app)
{
var basePath = "/v1";
app.UseSwagger(c =>
{
c.RouteTemplate = "api-docs/{documentName}/swagger.json";
c.SerializeAsV2 = true;
c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
{
swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{basePath}" } };
});
});
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("./v1/swagger.json", "Versioned API v1.0"); //
c.RoutePrefix = "api-docs";
});
return app;
}
and
public class SwaggerEnumParameterFilter : IParameterFilter
{
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
{
var type = context.ApiParameterDescription.Type;
if (type.IsEnum)
{
var values = Enum.GetValues(type);
var valuesArr = new OpenApiArray();
foreach (var value in values)
{
var item = new OpenApiObject
{
["name"] = new OpenApiString(Enum.GetName(type, value)),
["value"] = new OpenApiString(value.ToString())
};
valuesArr.Add(item);
}
parameter.Extensions.Add("x-ms-enum", new OpenApiObject
{
["name"] = new OpenApiString(type.Name),
["modelAsString"] = new OpenApiBoolean(true),
["values"] = valuesArr
});
}
}
}
and
public static class SwaggerGenOptionsExtensions
{
public static SwaggerGenOptions RegisterEnumSchemas(this SwaggerGenOptions options, Assembly assembly, string enumsNamespace)
{
var enums = from t in assembly.GetTypes()
where t.IsEnum && t.Namespace == enumsNamespace
select t;
foreach (var enumerate in enums)
{
var nullableEnumerate = typeof(Nullable<>).MakeGenericType(enumerate);
MapEnumType(options, enumerate, false);
MapEnumType(options, nullableEnumerate, true);
}
return options;
}
private static void MapEnumType(SwaggerGenOptions options, Type enumerate, bool nullable)
{
var underlyingEnum = nullable ? Nullable.GetUnderlyingType(enumerate) : enumerate;
options.MapType(enumerate, () => new OpenApiSchema
{
Type = "string",
Enum = underlyingEnum.GetEnumNames().Select(name => new OpenApiString(name)).Cast<IOpenApiAny>().ToList(),
Nullable = nullable
});
}
}
[update]
Trying BlueJayke's suggestion
回答1:
yeash yeash yeash... just do something like the following:
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)}";
}
}
and lets not forgot
public class EnumTypeSchemaFilter : ISchemaFilter {
public void Apply(Schema schema, SchemaFilterContext context) {
var typeInfo = context.SystemType.GetTypeInfo();
if (typeInfo.IsEnum) {
schema.Extensions.Add(
"x-ms-enum",
new {
name = typeInfo.Name,
modelAsString = false
});
}
}
}
and parameter file:
public class AutoRestParameterFilter : IParameterFilter
{
public void Apply(IParameter parameter, ParameterFilterContext context)
{
var type = context.ApiParameterDescription.Type;
if (type.IsEnum)
parameter.Extensions.Add( "x-ms-enum", new { name = type.Name, modelAsString = false });
}
}
hope that helps clear things up
来源:https://stackoverflow.com/questions/61022795/generating-swagger-json-with-backwardly-compatible-enum-using-serializeasv2