NET 5.0 Swagger API 自动生成MarkDown文档
基于 Swashbuckle.AspNetCore ,根据SwaggerGenerators生成的文档生成 MarkDown 文档。
文档功能:
[x] JSON 数据格式展示 Request 、Response 数据结构(支持实体多级引用)
[x] 生成 Request Body 、Response Body 示例参数
[x] 支持特性过滤Controller、Action
1、SwaggerDoc引用
主要接口
public interface ISwaggerDocGenerator
{
Task<MemoryStream> GetSwaggerDocStreamAsync(string name);
string GetSwaggerDoc(string name);
}
接口实现
/// <summary>
/// SwaggerDocGenerator
/// </summary>
public class SwaggerDocGenerator : ISwaggerDocGenerator
{
private readonly SwaggerGenerator _generator;
private IDictionary<string, OpenApiSchema> Schemas;
const string contentType = "application/json";
/// <summary>
/// SwaggerDocGenerator
/// </summary>
/// <param name="swagger"></param>
public SwaggerDocGenerator(SwaggerGenerator swagger)
{
_generator = swagger;
}
/// <summary>
/// 生成MarkDown
/// </summary>
/// <returns></returns>
public string GetSwaggerDoc(string name)
{
if (string.IsNullOrEmpty(name))
throw new Exception("name is null !");
var document = _generator.GetSwagger(name);
if (document == null)
throw new Exception("swagger is null !");
Schemas = document.Components.Schemas;
var markDown = new StringBuilder();
markDown.AppendLine(document?.Info?.Title.H1());//文档标题
markDown.AppendLine(document?.Info?.Description.Ref1());//文档描述
foreach (var path in document.Paths)
{
var openApiPath = path.Value;
var (flag, method, operation) = GetApiOperation(openApiPath);
if (flag == false)
continue;
var row = new StringBuilder();
var url = path.Key;
var title = operation.Summary ?? url;
var httpMethod = method;
var query = GetParameters(operation.Parameters);
var (requestExapmle, requestSchema) = GetRequestBody(operation.RequestBody);
var (responseExapmle, responseSchema) = GetResponses(operation.Responses);
row.AppendLine(title.H2());//接口名称
row.AppendLine("基本信息".H3().NewLine());//基本信息
row.AppendLine($"{"接口地址:".B()}{url}".Li().NewLine());
row.AppendLine($"{"请求方式:".B()}{httpMethod}".Li().NewLine());
if (httpMethod == "Post" || httpMethod == "Put")
{
row.AppendLine($"{"请求类型:".B()}{contentType}".Li().NewLine());
}
if (string.IsNullOrWhiteSpace(query) == false)//Query
{
row.AppendLine("Query".H3());
row.AppendLine(query);
}
if (string.IsNullOrWhiteSpace(requestSchema) == false)//RequestSchema
{
row.AppendLine("RequestSchema".H3());
row.AppendLine(requestSchema.Code());
}
if (string.IsNullOrWhiteSpace(requestExapmle) == false)//RequestBody
{
row.AppendLine("RequestBody".H3());
row.AppendLine(requestExapmle.Code());
}
if (string.IsNullOrWhiteSpace(responseSchema) == false)//ResponseSchema
{
row.AppendLine("ResponseSchema".H3());
row.AppendLine(responseSchema.Code());
}
if (string.IsNullOrWhiteSpace(responseExapmle) == false)//ResponseBody
{
row.AppendLine("ResponseBody".H3());
row.AppendLine(responseExapmle.Code());
}
if (string.IsNullOrWhiteSpace(row.ToString()) == false)
markDown.AppendLine(row.ToString().Br());
}
return markDown.ToString();
}
private (bool isSuccesss, string method, OpenApiOperation openApiOperation) GetApiOperation(OpenApiPathItem openApiPathItem)
{
var operations = openApiPathItem.Operations;
OpenApiOperation operation;
OperationType? operationType = null;
if (operations.ContainsKey(OperationType.Get))
operationType = OperationType.Get;
else if (operations.ContainsKey(OperationType.Post))
operationType = OperationType.Post;
else if (operations.ContainsKey(OperationType.Put))
operationType = OperationType.Put;
else if (operations.ContainsKey(OperationType.Patch))
operationType = OperationType.Patch;
else if (operations.ContainsKey(OperationType.Delete))
operationType = OperationType.Delete;
var flag = operations.TryGetValue(operationType.Value, out operation);
return (flag, operationType.Value.ToString(), operation);
}
private string GetParameters(IList<OpenApiParameter> apiParameters)
{
string str = null;
var isFirst = true;
foreach (var parameter in apiParameters)
{
var queryTitle = "|参数名称|参数类型|描述|".NewLine();
queryTitle += "|:----:|:----:|:----:|".NewLine();
var queryStr = $"|{parameter.Name}|{parameter.Schema.Type}|{parameter.Description}|".NewLine();
str += isFirst ? $"{queryTitle}{queryStr}" : queryStr;
isFirst = false;
}
return str;
}
private (string exampleJson, string schemaJson) GetRequestBody(OpenApiRequestBody body)
{
string exampleJson = null, schemaJson = null;
if (body != null && body.Content.ContainsKey(contentType))
{
var schema = body.Content[contentType].Schema;
exampleJson += GetExapmple(schema).ToJson();
schemaJson += GetModelInfo(schema, (id) => GetModelTProc(id)).ToJson();
}
return (exampleJson, schemaJson);
}
private (string exampleJson, string schemaJson) GetResponses(OpenApiResponses body)
{
string exampleJson = null, schemaJson = null;
if (body != null && body["200"].Content.ContainsKey(contentType))
{
var schema = body["200"].Content[contentType].Schema;
exampleJson += GetExapmple(schema).ToJson();
schemaJson += GetModelInfo(schema, (id) => GetModelTProc(id, false)).ToJson();
}
return (exampleJson, schemaJson);
}
private object GetExapmple(OpenApiSchema apiSchema)
{
object exapmle = null;
if (apiSchema.Type == null && apiSchema.Reference != null)//object
{
var key = apiSchema?.Reference?.Id;
exapmle = GetModelExample(key);
}
else if (apiSchema.Type == "array" && apiSchema.Items != null)//array
{
var key = apiSchema?.Items?.Reference?.Id;
if (key != null)
exapmle = new[] { GetModelExample(key) };
else if (key == null && apiSchema.Items.Type != null)
exapmle = new[] { GetDefaultValue(apiSchema.Items.Type) };
}
else
{
exapmle = GetDefaultValue(apiSchema.Type);
}
return exapmle;
}
private object GetModelExample(string key)
{
if (key != null && Schemas.ContainsKey(key))
{
var schema = Schemas.FirstOrDefault(x => x.Key == key).Value;
var exapmle = new ModelExample();
if (schema.Properties.Any())
{
foreach (var item in schema.Properties)
{
if (item.Value.Reference != null && Schemas.FirstOrDefault(x => x.Key == item.Value.Reference.Id).Value.Enum.Count == 0)
{
var objKey = item.Value.Reference.Id;
exapmle.Add(item.Key, GetModelExample(objKey));
}
else if (item.Value.Items != null)
{
var arrayKey = item.Value.Items.Reference.Id;
exapmle.Add(item.Key, new[] { GetModelExample(arrayKey) });
}
else
{
if (item.Value.Reference != null && Schemas.FirstOrDefault(x => x.Key == item.Value.Reference.Id).Value.Enum.Count != 0)
exapmle.Add(item.Key, 0);
else
exapmle.Add(item.Key, GetDefaultValue(item.Value.Format ?? item.Value.Type));
}
}
}
return exapmle;
}
return null;
}
private object GetModelInfo(OpenApiSchema apiSchema, Func<string, object> func)
{
object info = null;
var key = "";
if (apiSchema.Type == null && apiSchema.Reference != null)//object
key = apiSchema?.Reference?.Id;
else if (apiSchema.Type == "array" && apiSchema.Items != null)//array
key = apiSchema?.Items?.Reference?.Id ?? apiSchema.Items.Type;
else if (apiSchema.Type != null)
key = apiSchema.Type;
if (key != null)
info = func(key);
return info;
}
private object GetModelTProc(string key, bool isShowRequired = true)
{
if (key != null)
{
if (Schemas.ContainsKey(key))
{
var schema = Schemas.FirstOrDefault(x => x.Key == key).Value;
var info = new Dictionary<string, object>();
if (schema.Properties.Any())
{
foreach (var item in schema.Properties)
{
object obj = item.Value.Format ?? item.Value.Type ?? "object";
if (item.Value.Reference != null && Schemas.FirstOrDefault(x => x.Key == item.Value.Reference.Id).Value.Enum.Count == 0)
{
var objKey = item.Value.Reference.Id;
obj = GetModelTProc(objKey, isShowRequired);
}
else if (item.Value.Items != null)
{
var arrayKey = item.Value.Items.Reference.Id;
obj = GetModelTProc(arrayKey, isShowRequired);
}
else
{
if (item.Value.Reference != null && Schemas.FirstOrDefault(x => x.Key == item.Value.Reference.Id).Value.Enum.Count != 0)
obj = item.Value.Reference.Id;
}
if (isShowRequired)
{
var requestModelInfo = new RequestModelInfo
{
参数类型 = obj,
描述 = item.Value.Description,
是否必传 = schema.Required.Any(x => x == item.Key)
};
info.Add(item.Key, requestModelInfo);
}
else
{
var responseModelInfo = new ResponseModelInfo
{
参数类型 = obj,
描述 = item.Value.Description
};
info.Add(item.Key, responseModelInfo);
}
}
}
return info;
}
else
{
return key;
}
}
return null;
}
private Assembly GetAssembly()
{
return Assembly.GetExecutingAssembly();
}
private bool Valid<T>(string name)
{
var types = GetAssembly().GetTypes().Where(x => x.Name.EndsWith("Controller") && x.IsDefined(typeof(T))).Select(x => x.Name).ToArray();
return types.Any(x => x.ToLower().Contains(name.ToUpper()));
}
private object GetDefaultValue(string type)
{
var number = new string[] { "byte", "decimal", "double", "enum", "float", "int32", "int64", "sbyte", "short", "uint", "ulong", "ushort" };
if (number.Any(x => type == x))
return 0;
if (type == "string")
return "string";
if (type == "bool" || type == "boolean")
return false;
if (type == "date-time")
return DateTime.Now;
return null;
}
public async Task<MemoryStream> GetSwaggerDocStreamAsync(string name)
{
using var stream = new MemoryStream();
using var sw = new StreamWriter(stream);
var content = GetSwaggerDoc(name);
await sw.WriteLineAsync(content);
return stream;
}
}
2、Startup配置
注册SwaggerDoc服务
services.AddSwaggerDoc();//(用于MarkDown生成)
注册Swagger服务
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Swagger API 示例文档", Version = "v1",Description="API文档全部由代码自动生成" });
c.IncludeXmlComments("Samples.xml");
});
引用Swagger中间件
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Samples v1"));
3、生成MarkDown
/// <summary>
/// SwaggerController
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class SwaggerController : ControllerBase
{
/// <summary>
/// API文档导出
/// </summary>
[HttpGet]
public async Task<IActionResult> SwaggerDoc([FromServices] ISwaggerDocGenerator swaggerDocGenerator, [FromServices] IWebHostEnvironment environment)
{
var stream = await swaggerDocGenerator.GetSwaggerDocStreamAsync("v1");
var mime = "application/octet-stream";
var name = "SwaggerDoc.md";
return File(stream.ToArray(), mime,name);
}
}
4、生成示例

5、MarkDown转PDF
我是用的是 typora 编辑器,下载 pandoc 插件可以实现Marddown格式转换为PDF功能(免费)
如果需要样式调整,可以去https://theme.typora.io/选选

完整项目示例
地址(可以直接运行): https://github.com/lwc1st/SwaggerDoc
NET 5.0 Swagger API 自动生成MarkDown文档的更多相关文章
- Web Api 自动生成帮助文档
Web Api 自动生成帮助文档 新建Web Api项目之后,会在首页有API的导航菜单,点击即可看到API帮助文档,不过很遗憾,Description 是没有内容的. 怎么办呢? 第一步: 如果 ...
- WebApi使用swagger ui自动生成接口文档
之前就写到.最近正在使用webapi.这里介绍一个实用的东西swageer ui现在开发都是前后端分开.我们这里是给前端提供api.有时候对于一个api的描述,并不想专门写一份文档.很浪费时间.swa ...
- Web API 自动生成帮助文档并使用Web API Test Client 测试
之前在项目中有用到webapi对外提供接口,发现在项目中有根据webapi的方法和注释自动生成帮助文档,还可以测试webapi方法,功能很是强大,现拿出来与大家分享一下. 先看一下生成的webapi文 ...
- Web API 自动生成接口文档
1.添加NuGet程序包 Microsoft ASP.NET Web API 2.2 Help Page (这是微软官方的) A Simple Test Client for ASP.NET ...
- spring boot 中使用swagger 来自动生成接口文档
1.依赖包 <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swa ...
- WebAPI——自动生成帮助文档
Web Api 自动生成帮助文档 新建Web Api项目之后,会在首页有API的导航菜单,点击即可看到API帮助文档,不过很遗憾,Description 是没有内容的. 怎么办呢? 第一步: 如果 ...
- .net core 使用swagger自动生成接口文档
前言 swagger是一个api文档自动生动工具,还集成了在线调试. 可以为项目自动生成接口文档, 非常的方便快捷 Swashbuckle.AspNetCore 是一个开源项目,用于生成 ASP.N ...
- JApiDocs(自动生成接口文档神器)
JApiDocs教程 前言 作为一名优秀的程序员来说,由于涉及到要与前端进行对接,所以避免不了的就是写接口文档.写完接口文档,一旦代码返回结果,参数等出现变动,接口文档还得随之改动,十分麻烦,违背了我 ...
- Spring Boot(九)Swagger2自动生成接口文档和Mock模拟数据
一.简介 在当下这个前后端分离的技术趋势下,前端工程师过度依赖后端工程师的接口和数据,给开发带来了两大问题: 问题一.后端接口查看难:要怎么调用?参数怎么传递?有几个参数?参数都代表什么含义? 问题二 ...
随机推荐
- zsh terminal set infinity scroll height
zsh terminal set infinity scroll height zsh Terminal 开启无限滚动 https://stackoverflow.com/questions/2761 ...
- GitHub GraphQL API v4 & GitHub REST API v3
GitHub, GraphQL API, v4 ,REST API, v3, GraphQL, https://developer.github.com/v4/ https://developer.g ...
- free online business card generator
free online business card generator 免费在线名片生成器 https://www.logaster.cn/business-card/ https://www.chu ...
- svg insert shape string bug
svg insert shape string bug not support custom areaProps attributes ??? const svg = document.querySe ...
- svg click event bug & css pointer-events
svg click event bug & css pointer-events svg click event not working Error OK ??? css class /* d ...
- NGK公链存储技术,如何开创应用落地新格局?
尽管无人预测未来,但是资本的眼光总是那么灵敏,最近几年,国际资本市场纷纷将目光投到了公链市场上.从TPS高点备受抢占,再到DApp生态的不断涌现,再到目前Staking和Defi的新概念生态的不断发力 ...
- Git 学习相关笔记
Git Bash 相关命令学 基础概念 参考: https://www.cnblogs.com/gaoht/p/9087070.html https://www.runoob.com/git/git- ...
- .NET Core Swagger 的分组使, 以及相同Action能被多个分组公用,同时加载出尚未分组的数据出来
1.本文章参考 点击链接跳转 改写的 一对多分组模式.需要一对一的可以参考 2.本文主要讲的是 一对多 分组公用, 同时把尚未分组的加载出来 3.效果演示GIF图: 具体操作代码如下: 1.在项目创建 ...
- spring-ioc注解-理解2 零配置文件
没有xml配置文件下的对象注入,使用到一个Teacher类,Config配置类,Test测试类. 1.Teacher类 import lombok.Data; import org.springfra ...
- Win32Api -- 关闭当前应用
本文介绍Windows系统下使用Win32API获取当前应用并关闭的方法. 思路 使用EnumWindows接口枚举当前窗口; 过滤掉不可用.隐藏.最小化的窗口: 过滤掉子窗口: 通过标题.类名过滤掉 ...