基于 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文档的更多相关文章

  1. Web Api 自动生成帮助文档

    Web Api 自动生成帮助文档   新建Web Api项目之后,会在首页有API的导航菜单,点击即可看到API帮助文档,不过很遗憾,Description 是没有内容的. 怎么办呢? 第一步: 如果 ...

  2. WebApi使用swagger ui自动生成接口文档

    之前就写到.最近正在使用webapi.这里介绍一个实用的东西swageer ui现在开发都是前后端分开.我们这里是给前端提供api.有时候对于一个api的描述,并不想专门写一份文档.很浪费时间.swa ...

  3. Web API 自动生成帮助文档并使用Web API Test Client 测试

    之前在项目中有用到webapi对外提供接口,发现在项目中有根据webapi的方法和注释自动生成帮助文档,还可以测试webapi方法,功能很是强大,现拿出来与大家分享一下. 先看一下生成的webapi文 ...

  4. Web API 自动生成接口文档

    1.添加NuGet程序包 Microsoft ASP.NET Web API 2.2 Help Page      (这是微软官方的) A Simple Test Client for ASP.NET ...

  5. spring boot 中使用swagger 来自动生成接口文档

    1.依赖包 <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swa ...

  6. WebAPI——自动生成帮助文档

    Web Api 自动生成帮助文档   新建Web Api项目之后,会在首页有API的导航菜单,点击即可看到API帮助文档,不过很遗憾,Description 是没有内容的. 怎么办呢? 第一步: 如果 ...

  7. .net core 使用swagger自动生成接口文档

     前言 swagger是一个api文档自动生动工具,还集成了在线调试. 可以为项目自动生成接口文档, 非常的方便快捷 Swashbuckle.AspNetCore 是一个开源项目,用于生成 ASP.N ...

  8. JApiDocs(自动生成接口文档神器)

    JApiDocs教程 前言 作为一名优秀的程序员来说,由于涉及到要与前端进行对接,所以避免不了的就是写接口文档.写完接口文档,一旦代码返回结果,参数等出现变动,接口文档还得随之改动,十分麻烦,违背了我 ...

  9. Spring Boot(九)Swagger2自动生成接口文档和Mock模拟数据

    一.简介 在当下这个前后端分离的技术趋势下,前端工程师过度依赖后端工程师的接口和数据,给开发带来了两大问题: 问题一.后端接口查看难:要怎么调用?参数怎么传递?有几个参数?参数都代表什么含义? 问题二 ...

随机推荐

  1. codeforces 1077D Cutting Out 【二分】

    题目:戳这里 题意:给n个数的数组,要求找k个数满足,这k个数在数组中出现的次数最多. 解题思路:k个数每个数出现次数都要最大化,可以想到二分下限,主要是正确的二分不好写. 附ac代码: 1 #inc ...

  2. c# 类(2)

    构造函数 和 析构函数 Constructors and destructors 构造函数是一个特殊的函数,当实例化一个类的时候自动调用这个函数,无返回值(不用定义返回类型)普通函数的定义 publi ...

  3. 高并发之Phaser、ReadWriteLock、StampedLock

    本系列研究总结高并发下的几种同步锁的使用以及之间的区别,分别是:ReentrantLock.CountDownLatch.CyclicBarrier.Phaser.ReadWriteLock.Stam ...

  4. Python+OpenCV+图片旋转并用原底色填充新四角

    import cv2 from math import fabs, sin, cos, radians import numpy as np from scipy.stats import mode ...

  5. webpack 5 模块联合

    webpack 5 模块联合 webpack 5 https://webpack.docschina.org/concepts/module-federation/ https://github.co ...

  6. 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 ...

  7. TypeScript 4.0 New Features

    TypeScript 4.0 New Features $ npm install typescript@beta https://devblogs.microsoft.com/typescript/ ...

  8. copyright@xgqfrms

    copyright@xgqfrms copyright & seo ## refs *** <div> <a href="https://info.flagcoun ...

  9. Rust learning notes

    Rust learning notes Rust Version 1.42.0 $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs ...

  10. vue的filter用法,检索内容

    var app5 = new Vue({ el: '#app5', data: { shoppingList: [ "Milk", "Donuts", &quo ...