基于 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 878A

    A. Short Program time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...

  2. STM32F107移植LWIP

    STM32F107上移植LWIP2.0.3 因为最近需要在STM32F107上实现TCP/IP协议栈,所以网上查了一下,准备使用LWIP,虽然大多数用的是1.4.1版本但是官方说2系大版本修复了1.4 ...

  3. h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated

    Reference 问题 ... h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype ...

  4. Python_K-means算法

    from sklearn import cluster [centroid, label, inertia] = cluster.k_means(data_to_be_classified, num_ ...

  5. Vue UI lib missing vue bug

    Vue UI lib missing vue bug Error Uncaught TypeError: Cannot read property 'prototype' of undefined a ...

  6. yarn global add !== yarn add global

    yarn global add !== yarn add global yarn does not exist the --global flag, but exits yarn global com ...

  7. what's the print number means after called the setTimeout function in Chrome console?

    what's the print number means after called the setTimeout function in Chrome console? javascript fun ...

  8. convert image to base64 in javascript

    convert image to base64 in javascript "use strict"; /** * * @author xgqfrms * @license MIT ...

  9. linux move file / folder bash command

    linux move file / folder bash command mv $ which mv $ man mv # mv [-f] source target/ target folder ...

  10. Flutter-desktop

    flutter-desktop-embedding video windows $ flutter channel master && flutter upgrade 更新你的 flu ...