NET 5.0 Swagger API 自動生成MarkDown文件
阿新 • • 發佈:2021-03-14
[TOC]
> 基於 Swashbuckle.AspNetCore ,根據SwaggerGenerators生成的文件生成 MarkDown 文件。
>
> 文件功能:
>
> - [x] JSON 資料格式展示 Request 、Response 資料結構(支援實體多級引用)
>
> - [x] 生成 Request Body 、Response Body 示例引數
> - [x] 支援特性過濾Controller、Action
## 1、SwaggerDoc引用
### 主要介面
```C#
public interface ISwaggerDocGenerator
{
Task GetSwaggerDocStreamAsync(string name);
string GetSwaggerDoc(string name);
}
```
### 介面實現
```C#
///
/// SwaggerDocGenerator
///
public class SwaggerDocGenerator : ISwaggerDocGenerator
{
private readonly SwaggerGenerator _generator;
private IDictionary Schemas;
const string contentType = "application/json";
///
/// SwaggerDocGenerator
///
///
public SwaggerDocGenerator(SwaggerGenerator swagger)
{
_generator = swagger;
}
///
/// 生成MarkDown
///
///
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 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 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();
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(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 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服務
```C#
services.AddSwaggerDoc();//(用於MarkDown生成)
```
### 註冊Swagger服務
```C#
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Swagger API 示例文件", Version = "v1",Description="API文件全部由程式碼自動生成" });
c.IncludeXmlComments("Samples.xml");
});
```
### 引用Swagger中介軟體
```C#
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Samples v1"));
```
## 3、生成MarkDown
```C#
///
/// SwaggerController
///
[Route("api/[controller]/[action]")]
[ApiController]
public class SwaggerController : ControllerBase
{
///
/// API文件匯出
///
[HttpGet]
public async Task 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、生成示例
![SwaggerDoc.png](https://github.com/lwc1st/SwaggerDoc/blob/master/Doc/SwaggerDoc.png?raw=true)
## 5、MarkDown轉PDF
我是用的是 [typora](https://www.typora.io/) 編輯器,下載 [pandoc](https://github.com/jgm/pandoc/releases) 外掛可以實現Marddown格式轉換為PDF功能(免費)
如果需要樣式調整,可以去https://theme.typora.io/ 選選
![ToPDF.png](https://github.com/lwc1st/SwaggerDoc/blob/master/Doc/ToPDF.png?raw=true)
## 完整專案示例
地址(可以直接執行): https://github.com/lwc1st/Sw