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模拟数据
一.简介 在当下这个前后端分离的技术趋势下,前端工程师过度依赖后端工程师的接口和数据,给开发带来了两大问题: 问题一.后端接口查看难:要怎么调用?参数怎么传递?有几个参数?参数都代表什么含义? 问题二 ...
随机推荐
- codeforces 1077D Cutting Out 【二分】
题目:戳这里 题意:给n个数的数组,要求找k个数满足,这k个数在数组中出现的次数最多. 解题思路:k个数每个数出现次数都要最大化,可以想到二分下限,主要是正确的二分不好写. 附ac代码: 1 #inc ...
- c# 类(2)
构造函数 和 析构函数 Constructors and destructors 构造函数是一个特殊的函数,当实例化一个类的时候自动调用这个函数,无返回值(不用定义返回类型)普通函数的定义 publi ...
- 高并发之Phaser、ReadWriteLock、StampedLock
本系列研究总结高并发下的几种同步锁的使用以及之间的区别,分别是:ReentrantLock.CountDownLatch.CyclicBarrier.Phaser.ReadWriteLock.Stam ...
- Python+OpenCV+图片旋转并用原底色填充新四角
import cv2 from math import fabs, sin, cos, radians import numpy as np from scipy.stats import mode ...
- webpack 5 模块联合
webpack 5 模块联合 webpack 5 https://webpack.docschina.org/concepts/module-federation/ https://github.co ...
- How to using PyPI publish a Python package
How to using PyPI publish a Python package PyPI & Python package https://pypi.org/ main make a f ...
- TypeScript 4.0 New Features
TypeScript 4.0 New Features $ npm install typescript@beta https://devblogs.microsoft.com/typescript/ ...
- copyright@xgqfrms
copyright@xgqfrms copyright & seo ## refs *** <div> <a href="https://info.flagcoun ...
- Rust learning notes
Rust learning notes Rust Version 1.42.0 $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs ...
- vue的filter用法,检索内容
var app5 = new Vue({ el: '#app5', data: { shoppingList: [ "Milk", "Donuts", &quo ...