.NET Core API自定义Swashbuckle Schema生成与UI展示XML注释的实现方法
整体实现思路
完全可以基于Swashbuckle.AspNetCore的内置扩展点完成需求,不需要从零实现自定义Schema生成逻辑,整个改造分为后端Schema生成逻辑扩展、前端UI适配两个模块,兼容OpenAPI规范的同时,不需要大改现有代码结构。
后端Swagger生成端改造
Swashbuckle默认的XML注释处理逻辑仅能识别<summary>、<param>等标准标签,既不会解析<remarks>的注释内容,也不会识别自定义XML节点。你可以通过实现自定义Schema过滤器,补充这部分解析能力:
- 第一步:确保项目开启了XML文档生成(项目属性->生成->输出,勾选“XML文档文件”,记录生成的XML文件路径)
- 第二步:编写自定义
ISchemaFilter,核心处理逻辑包括:- 按反射的成员信息匹配XML文档中对应的注释节点
- 解析
<remarks>节点内容,直接拼接在原有summary生成的Description内容后,利用Markdown加粗格式区分段落 - 递归解析自定义XML标签节点(如
<original_info_painfully_discovered_along_the_way>),按照OpenAPI规范要求加x-前缀,存入Schema的Extensions扩展字典中 - 对顶层DTO类本身的注释节点做同样处理,即可同步展示类级别的remarks内容
- 第三步:将自定义过滤器注册到Swagger服务配置中,替换掉默认重复的XML注释处理逻辑
核心过滤器实现参考代码:
public class CustomXmlCommentsSchemaFilter : ISchemaFilter { private readonly XPathNavigator _xmlNavigator; public CustomXmlCommentsSchemaFilter(string xmlDocPath) { var xmlDoc = new XPathDocument(xmlDocPath); _xmlNavigator = xmlDoc.CreateNavigator(); } public void Apply(OpenApiSchema schema, SchemaFilterContext context) { // 匹配属性级别的注释 if (context.MemberInfo is PropertyInfo propInfo) { var memberXPath = $"P:{context.Type.FullName}.{propInfo.Name}"; var memberNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{memberXPath}']"); if (memberNode == null) return; // 解析标准summary节点 var summaryText = memberNode.SelectSingleNode("summary")?.Value.Trim() ?? string.Empty; // 解析remarks节点 var remarksText = memberNode.SelectSingleNode("remarks")?.Value.Trim() ?? string.Empty; // 拼接为可直接展示的Description内容 schema.Description = string.Join("\n\n", new[] { summaryText, string.IsNullOrEmpty(remarksText) ? string.Empty : $"**备注说明**\n{remarksText}" }).Trim(); // 解析自定义XML标签节点 var customInfoNode = memberNode.SelectSingleNode("original_info_painfully_discovered_along_the_way"); if (customInfoNode != null) { var customData = new OpenApiObject(); foreach (XPathNavigator child in customInfoNode.Children) { customData[child.Name] = new OpenApiString(child.Value.Trim()); } // 加x-前缀符合OpenAPI扩展规范 schema.Extensions["x-original_info_painfully_discovered_along_the_way"] = customData; } } // 匹配类级别的注释 else { var typeXPath = $"T:{context.Type.FullName}"; var typeNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{typeXPath}']"); if (typeNode == null) return; // 对类的remarks、自定义标签执行与属性一致的解析逻辑 } } }
注册过滤器的配置示例:
services.AddSwaggerGen(opt => { opt.SwaggerDoc("v1", new OpenApiInfo { Title = "Your API", Version = "v1" }); // 传入XML文档路径注册自定义过滤器 var xmlPath = Path.Combine(AppContext.BaseDirectory, "YourAssemblyName.xml"); opt.SchemaFilter<CustomXmlCommentsSchemaFilter>(xmlPath); });
注意:OpenAPI规范要求所有自定义扩展字段必须添加
x-前缀,否则会被序列化逻辑过滤,也无法通过OpenAPI规范校验,你之前预期的自定义字段结构需要对应调整前缀,才能被Swashbuckle正常输出。
前端Swagger UI适配
根据你的展示需求有两种适配方案,无需完全重写默认的index.html页面:
- 针对remarks内容的展示:因为我们已经把remarks内容拼接进了Schema的Description字段,默认Swagger UI本身就会自动渲染该字段的Markdown格式内容,完全不需要前端改造,即可直接展示在Model区块中。
- 针对自定义扩展字段的展示:默认Swagger UI不会主动渲染非规范定义的扩展字段,此时不需要改动UI的主体布局,只需要通过Swashbuckle提供的脚本注入能力,加载一小段自定义JS,在UI渲染完成后,读取Swagger JSON中对应属性的
x-开头扩展字段,将内容追加渲染到对应属性的展示块中即可。
注入脚本的配置示例:app.UseSwaggerUI(opt => { opt.SwaggerEndpoint("/swagger/v1/swagger.json", "Your API v1"); // 注入自定义渲染脚本 opt.InjectJavascript("/js/swagger-custom-extension-render.js"); });
最终生成的Swagger JSON结构将完全符合你的预期,同时兼容OpenAPI规范。
内容的提问来源于stack exchange,提问作者Andy Stagg
相关产品推荐
相关产品推荐

