Swagger(C# 的 Swashbuckle)将 Mongo ObjectId 显示为多个字段而不是单个字符串
Swagger (Swashbuckle for C#) shows Mongo ObjectId as several fields instead of single string
我有带 ObjectId
参数的控制器方法:
[ProducesResponseType(200, Type = typeof(Test))]
[HttpGet]
[Route("{id}")]
public IActionResult Get(ObjectId id)
{...
为此 API 方法 swagger 生成了一个包含复杂 ObjectId 模型和字符串 Id 而不是单个字符串参数的表单:
如何删除多余的字段并仅保留字符串 ID?
可以过滤 swagger 表单生成器的输出字段:
public class SwaggerOperationFilter : IOperationFilter
{
private readonly IEnumerable<string> objectIdIgnoreParameters = new[]
{
nameof(ObjectId.Timestamp),
nameof(ObjectId.Machine),
nameof(ObjectId.Pid),
nameof(ObjectId.Increment),
nameof(ObjectId.CreationTime)
};
public void Apply(Operation operation, OperationFilterContext context)
{
operation.Parameters = operation.Parameters.Where(x =>
x.In != "query" || objectIdIgnoreParameters.Contains(x.Name) == false
).ToList();
}
}
并在 Startup.cs
中使用此过滤器:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen(options =>
{
...
options.OperationFilter<SwaggerOperationFilter>();
});
...
结果,我们只有 Id 字段:
找出 from another 也解决了这个问题:
services.AddMvc(options =>
{
...
options.ModelMetadataDetailsProviders.Add(
new BindingSourceMetadataProvider(typeof(ObjectId), BindingSource.Special));
});
我有同样的问题,但稍微大一点。在我的 API 方法中,我使用 ObjectId:
- 作为路由参数
public IActionResult Get(ObjectId id)
,
- 在类内作为查询参数
public IActionResult Get([FromQuery] ClassWithObjectId filter)
,
- 在 POST 个机构中
public IActionResult Post([FromBody] ClassWithObjectId form)
- 在响应对象中
此外,我还有 <summary>
标签和 ObjectId 属性,我想在夸张的描述中展示它。
所以很难强迫 Swashbuckle 尊重所有这些案例,但我想我做到了!
我们需要两个过滤器:
- ObjectIdOperationFilter 强制 Swashbuckle 在属性(路由、查询)中尊重 ObjectId
- ObjectIdSchemaFilter 强制 Swashbuckle 在正文中尊重 ObjectId(在请求和响应中)
关于 swagger 设置:
//piece of code to add all XML Documentation files. Ignore if you don't need them
var swaggerFiles = new string[] { "SwaggerAPI.xml", "SwaggerApplicationAPI.xml" }
.Select(fileName => Path.Combine(System.AppContext.BaseDirectory, fileName))
.Where(filePath => File.Exists(filePath));
foreach (var filePath in swaggerFiles)
options.IncludeXmlComments(filePath);
//we have to pass swaggerFiles here to add description to ObjectId props
//don't pass them if you won't
options.OperationFilter<ObjectIdOperationFilter>(swaggerFiles);
options.SchemaFilter<ObjectIdSchemaFilter>();
ObjectIdOperationFilter.cs:
(所有使用 XML 的方法我都取自 Swashbuckle.AspNetCore repository)
public class ObjectIdOperationFilter : IOperationFilter
{
//prop names we want to ignore
private readonly IEnumerable<string> objectIdIgnoreParameters = new[]
{
"Timestamp",
"Machine",
"Pid",
"Increment",
"CreationTime"
};
private readonly IEnumerable<XPathNavigator> xmlNavigators;
public ObjectIdOperationFilter(IEnumerable<string> filePaths)
{
xmlNavigators = filePaths != null
? filePaths.Select(x => new XPathDocument(x).CreateNavigator())
: Array.Empty<XPathNavigator>();
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
//for very parameter in operation check if any fields we want to ignore
//delete them and add ObjectId parameter instead
foreach (var p in operation.Parameters.ToList())
if (objectIdIgnoreParameters.Any(x => p.Name.EndsWith(x)))
{
var parameterIndex = operation.Parameters.IndexOf(p);
operation.Parameters.Remove(p);
var dotIndex = p.Name.LastIndexOf(".");
if (dotIndex > -1)
{
var idName = p.Name.Substring(0, dotIndex);
if (!operation.Parameters.Any(x => x.Name == idName))
{
operation.Parameters.Insert(parameterIndex, new OpenApiParameter()
{
Name = idName,
Schema = new OpenApiSchema()
{
Type = "string",
Format = "24-digit hex string"
},
Description = GetFieldDescription(idName, context),
Example = new OpenApiString(ObjectId.Empty.ToString()),
In = p.In,
});
}
}
}
}
//get description from XML
private string GetFieldDescription(string idName, OperationFilterContext context)
{
var name = char.ToUpperInvariant(idName[0]) + idName.Substring(1);
var classProp = context.MethodInfo.GetParameters().FirstOrDefault()?.ParameterType?.GetProperties().FirstOrDefault(x => x.Name == name);
var typeAttr = classProp != null
? (DescriptionAttribute)classProp.GetCustomAttribute<DescriptionAttribute>()
: null;
if (typeAttr != null)
return typeAttr?.Description;
if (classProp != null)
foreach (var xmlNavigator in xmlNavigators)
{
var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(classProp);
var propertySummaryNode = xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{propertyMemberName}']/summary");
if (propertySummaryNode != null)
return XmlCommentsTextHelper.Humanize(propertySummaryNode.InnerXml);
}
return null;
}
}
ObjectIdSchemaFilter.cs:
public class ObjectIdSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type == typeof(ObjectId))
{
schema.Type = "string";
schema.Format = "24-digit hex string";
schema.Example = new OpenApiString(ObjectId.Empty.ToString());
}
}
}
而且有效!
我有带 ObjectId
参数的控制器方法:
[ProducesResponseType(200, Type = typeof(Test))]
[HttpGet]
[Route("{id}")]
public IActionResult Get(ObjectId id)
{...
为此 API 方法 swagger 生成了一个包含复杂 ObjectId 模型和字符串 Id 而不是单个字符串参数的表单:
可以过滤 swagger 表单生成器的输出字段:
public class SwaggerOperationFilter : IOperationFilter
{
private readonly IEnumerable<string> objectIdIgnoreParameters = new[]
{
nameof(ObjectId.Timestamp),
nameof(ObjectId.Machine),
nameof(ObjectId.Pid),
nameof(ObjectId.Increment),
nameof(ObjectId.CreationTime)
};
public void Apply(Operation operation, OperationFilterContext context)
{
operation.Parameters = operation.Parameters.Where(x =>
x.In != "query" || objectIdIgnoreParameters.Contains(x.Name) == false
).ToList();
}
}
并在 Startup.cs
中使用此过滤器:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen(options =>
{
...
options.OperationFilter<SwaggerOperationFilter>();
});
...
结果,我们只有 Id 字段:
找出
services.AddMvc(options =>
{
...
options.ModelMetadataDetailsProviders.Add(
new BindingSourceMetadataProvider(typeof(ObjectId), BindingSource.Special));
});
我有同样的问题,但稍微大一点。在我的 API 方法中,我使用 ObjectId:
- 作为路由参数
public IActionResult Get(ObjectId id)
, - 在类内作为查询参数
public IActionResult Get([FromQuery] ClassWithObjectId filter)
, - 在 POST 个机构中
public IActionResult Post([FromBody] ClassWithObjectId form)
- 在响应对象中
此外,我还有 <summary>
标签和 ObjectId 属性,我想在夸张的描述中展示它。
所以很难强迫 Swashbuckle 尊重所有这些案例,但我想我做到了!
我们需要两个过滤器:
- ObjectIdOperationFilter 强制 Swashbuckle 在属性(路由、查询)中尊重 ObjectId
- ObjectIdSchemaFilter 强制 Swashbuckle 在正文中尊重 ObjectId(在请求和响应中)
关于 swagger 设置:
//piece of code to add all XML Documentation files. Ignore if you don't need them
var swaggerFiles = new string[] { "SwaggerAPI.xml", "SwaggerApplicationAPI.xml" }
.Select(fileName => Path.Combine(System.AppContext.BaseDirectory, fileName))
.Where(filePath => File.Exists(filePath));
foreach (var filePath in swaggerFiles)
options.IncludeXmlComments(filePath);
//we have to pass swaggerFiles here to add description to ObjectId props
//don't pass them if you won't
options.OperationFilter<ObjectIdOperationFilter>(swaggerFiles);
options.SchemaFilter<ObjectIdSchemaFilter>();
ObjectIdOperationFilter.cs:
(所有使用 XML 的方法我都取自 Swashbuckle.AspNetCore repository)
public class ObjectIdOperationFilter : IOperationFilter
{
//prop names we want to ignore
private readonly IEnumerable<string> objectIdIgnoreParameters = new[]
{
"Timestamp",
"Machine",
"Pid",
"Increment",
"CreationTime"
};
private readonly IEnumerable<XPathNavigator> xmlNavigators;
public ObjectIdOperationFilter(IEnumerable<string> filePaths)
{
xmlNavigators = filePaths != null
? filePaths.Select(x => new XPathDocument(x).CreateNavigator())
: Array.Empty<XPathNavigator>();
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
//for very parameter in operation check if any fields we want to ignore
//delete them and add ObjectId parameter instead
foreach (var p in operation.Parameters.ToList())
if (objectIdIgnoreParameters.Any(x => p.Name.EndsWith(x)))
{
var parameterIndex = operation.Parameters.IndexOf(p);
operation.Parameters.Remove(p);
var dotIndex = p.Name.LastIndexOf(".");
if (dotIndex > -1)
{
var idName = p.Name.Substring(0, dotIndex);
if (!operation.Parameters.Any(x => x.Name == idName))
{
operation.Parameters.Insert(parameterIndex, new OpenApiParameter()
{
Name = idName,
Schema = new OpenApiSchema()
{
Type = "string",
Format = "24-digit hex string"
},
Description = GetFieldDescription(idName, context),
Example = new OpenApiString(ObjectId.Empty.ToString()),
In = p.In,
});
}
}
}
}
//get description from XML
private string GetFieldDescription(string idName, OperationFilterContext context)
{
var name = char.ToUpperInvariant(idName[0]) + idName.Substring(1);
var classProp = context.MethodInfo.GetParameters().FirstOrDefault()?.ParameterType?.GetProperties().FirstOrDefault(x => x.Name == name);
var typeAttr = classProp != null
? (DescriptionAttribute)classProp.GetCustomAttribute<DescriptionAttribute>()
: null;
if (typeAttr != null)
return typeAttr?.Description;
if (classProp != null)
foreach (var xmlNavigator in xmlNavigators)
{
var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(classProp);
var propertySummaryNode = xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{propertyMemberName}']/summary");
if (propertySummaryNode != null)
return XmlCommentsTextHelper.Humanize(propertySummaryNode.InnerXml);
}
return null;
}
}
ObjectIdSchemaFilter.cs:
public class ObjectIdSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type == typeof(ObjectId))
{
schema.Type = "string";
schema.Format = "24-digit hex string";
schema.Example = new OpenApiString(ObjectId.Empty.ToString());
}
}
}
而且有效!