1. 程式人生 > 實用技巧 >C#根據反射動態建立ShowDoc介面文字資訊

C#根據反射動態建立ShowDoc介面文字資訊

我目前每天主要工作以開發api為主,這都離不開介面檔案。如果遠端對接的話前端總說Swagger不清晰,只能重新找一下新的介面檔案。ShowDoc就是一個不錯的選擇,簡潔、大方、靈活部署。

但是話說回來,既然是檔案每個介面你都得寫。總感覺這樣效率太慢了,能不能自己生成一下,自己只要Ctrl+C、Ctrl+V就萬事大吉了。

早就想寫一下,今天抽空做了一下(後期我會繼續完善,時間、精力有限)。提前說好,我只寫了一個查詢的。而且也不可能說是生成了就不用改了,裡面的文字資訊全都符合各位同學的預期。但至少百分之八十的介面只要已生成直接copy就ok,還有一些個別介面... ...

一般來說,分頁查詢的響應資訊結構都是一樣的。不同的介面資料不同而已,所以返回的那個實體物件就反射那個物件。我是在屬性上標註特性以獲得相應註釋資訊。

首先新建一個Api專案

定義一個實體類和要返回的資訊類。

public class Products
{
[DescriptionAttribute("資料id")]
public int id { get; set; }
[DescriptionAttribute("商品名稱")]
public string productNams { get; set; }
[DescriptionAttribute("商品價格")]
public float price { get; set; }
} /// <summary>
/// 通用返回資訊類
/// </summary>
public class MessageModel<T> where T : class
{
[DescriptionAttribute("狀態碼")]
public int code { get; set; } = ;
/// <summary>
/// 操作是否成功
/// </summary>
[DescriptionAttribute("操作是否成功")]
public bool success { get; set; } = false;
/// <summary>
/// 返回資訊
/// </summary>
[DescriptionAttribute("返回資訊")]
public string msg { get; set; } = "伺服器異常";
/// <summary>
/// 返回資料集合
/// </summary>
[DescriptionAttribute("返回資料集合")]
public T response { get; set; } } /// <summary>
/// 通用分頁資訊類
/// </summary>
public class PageModel<T>
{
/// <summary>
/// 當前頁標
/// </summary>
[DescriptionAttribute("當前頁標")]
public int pageIndex { get; set; };
/// <summary>
/// 總頁數
/// </summary>
[DescriptionAttribute("總頁數")]
public int pageCount { get; set; };
/// <summary>
/// 資料總數
/// </summary>
[DescriptionAttribute("資料總數")]
public int dataCount { get; set; };
/// <summary>
/// 每頁大小
/// </summary>
[DescriptionAttribute("每頁大小")]
public int PageSize { set; get; }
/// <summary>
/// 返回資料
/// </summary>
[DescriptionAttribute("返回的資料集合")]
public T[] data { get; set; } }

寫兩個特性,一個用來標註屬性資訊,一個用來標註search查詢物件中的引數(我這邊分頁查詢,查詢引數傳json物件字串,pageIndex和pageSize除外)。

//類和類中的屬性資訊用這個特性
public class DescriptionAttribute : Attribute
{
public string _details = string.Empty;
public DescriptionAttribute(string details)
{
this._details = details;
}
}

//介面方法中的search引數用這個特性
public class SearchAttribute : Attribute
{
public string _details = string.Empty;
public SearchAttribute(string details)
{
this._details = details;
}
}

將要請求的ip地址寫入appsettings.json中

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"requestUrl": {
"ip": "http://127.0.0.1:5001"
}
}

寫好要查詢的介面,待會兒生成這個介面的介面檔案資訊

[Route("api/[controller]/[action]")]
[ApiController]
public class InstanceController : Controller
{
public InstanceController()
{ } [HttpGet]
[DescriptionAttribute("獲取所有商品資料")]
[SearchAttribute("{\"eId\": \"裝置id\",\"startTime\": \"2020-06-05\",\"endTime\": \"2020-06-06\"}")]
public async Task<MessageModel<PageModel<Products>>> GetAllDatas(string search = "", int pageIndex = , int pageSize = 24)
{
var list = new List<Products>()
{
new Products{ id=,productNams="商品1",price=13.6f},
new Products{ id=,productNams="商品2",price=14.6f},
new Products{ id=,productNams="商品3",price=15.6f}
}.ToArray();
return new MessageModel<PageModel<Products>>()
{
success = true,
msg = "資料獲取成功",
response = new PageModel<Products>()
{
pageIndex = pageIndex,
pageCount = Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(list.Length) / pageSize)),
dataCount = list.Length,
PageSize = pageSize,
data = list
}
};
}
}

再寫一個介面,專門用來查詢指定介面的資訊。兩個引數controlleName(控制器名稱)、apiMethodsName(介面名稱)

[Route("api/[controller]/[action]")]
[ApiController]
public class ShowDocFileControlle : Controller
{
private readonly IHostingEnvironment _hostingEnvironment;
private IConfiguration _configuration;
public ShowDocFileControlle(IHostingEnvironment hostingEnvironment,
IConfiguration configuration)
{
_hostingEnvironment = hostingEnvironment;
_configuration = configuration;
}/// <summary>
/// 反射獲取指定介面的資訊
/// </summary>
/// <returns></returns>
[HttpGet("{controlleName}/{apiMethodsName}")]
public async Task<IActionResult> GetShowDocApiFiles(string controlleName, string apiMethodsName)
{
#region 首先拿到要操作的檔案
//獲取檔案 路徑
string webRootPath = _hostingEnvironment.WebRootPath + @"\ApiInfo.txt";
//得到檔案流
FileStream stream = new FileStream(webRootPath, FileMode.Create, FileAccess.Write);
//建立寫入的檔案流物件
StreamWriter writer = new StreamWriter(stream);
#endregion
try
{
#region 根據引數反射操作對應的物件
writer.WriteLine("**簡要描述:** ");
writer.WriteLine("");
//根據引數controlleName得到型別
Type type = Type.GetType($"ReflectionShowDoc.Controllers.{controlleName}");
//根據型別建立該物件的例項
object instance = Activator.CreateInstance(type);
//再根據引數apiMethodsName得到對應的方法
MethodInfo method = type.GetMethod($"{apiMethodsName}");
#endregion #region 判斷Api方法上是否有DescriptionAttribute這個特性,有就獲取值
if (method.IsDefined(typeof(DescriptionAttribute), true))
{
//例項化得到一個DescriptionAttribute型別
//通過反射建立物件
DescriptionAttribute attribute = (DescriptionAttribute)method.GetCustomAttribute(typeof(DescriptionAttribute), true);
writer.WriteLine($"{attribute._details}"); }
else
writer.WriteLine($"介面未標明註釋");
#endregion #region 根據引數controlleName與apiMethodsName得到請求的url,ip建議寫到配置檔案中,讀也只讀配置檔案
writer.WriteLine("");
writer.WriteLine($"**請求URL:**");
writer.WriteLine("");
StringBuilder builder = new StringBuilder(@$"- `{_configuration["requestUrl:ip"]}/api/");
builder.Append($"{controlleName}/{apiMethodsName}`");
writer.WriteLine(builder.ToString());
writer.WriteLine("");
writer.WriteLine($"**請求方式:**");
writer.WriteLine("");
#endregion
#region 根據抽象父類HttpMethodAttribute得到介面的請求型別
if (method.IsDefined(typeof(HttpMethodAttribute), true))
{
//通過反射建立物件
HttpMethodAttribute attribute = (HttpMethodAttribute)method.GetCustomAttribute(typeof(HttpMethodAttribute), true);
writer.WriteLine($"- {attribute.HttpMethods.ToArray()[0]}"); }
#endregion #region 一般分頁查詢這些引數都是定好的,基本不會變
writer.WriteLine("");
writer.WriteLine($"**引數:** ");
writer.WriteLine("");
writer.WriteLine($"|引數名|必選|型別|說明|");
writer.WriteLine($"|:---- |:---|:----- |----- |");
writer.WriteLine($"|search |否 |string |查詢的物件|");
writer.WriteLine($"|pageIndex |是 |int | 頁碼 |");
writer.WriteLine($"|pageSize |是 |int | 頁面展示的資料量 |");
#endregion #region 引數search是一個json字串,這裡也通過特性標註,在例項化的時候獲取
writer.WriteLine($"**引數search所需引數及傳參示例**");
writer.WriteLine("``` ");
if (method.IsDefined(typeof(SearchAttribute), true))
{
//例項化得到一個SearchAttribute型別
//通過反射建立物件
SearchAttribute attribute = (SearchAttribute)method.GetCustomAttribute(typeof(SearchAttribute), true);
writer.WriteLine($"{attribute._details}");
writer.WriteLine("");
}
writer.WriteLine("將查詢的search物件序列化之後傳過來");
writer.WriteLine($"`{builder.ToString().Replace("-", string.Empty).Replace("`", string.Empty)}" + "?pageIndex=1&pageSize=30&search=serializeObject`");
writer.WriteLine("``` ");
writer.WriteLine("");
#endregion #region 因為要拿到響應的返回引數,所以這裡動態呼叫一下方法,取得第一頁的資料作為返回的資料示例
writer.WriteLine($" **返回示例**");
//這三個引數基本不會變
Type[] paramsType = new Type[];
paramsType[] = Type.GetType("System.String");
paramsType[] = Type.GetType("System.Int32");
paramsType[] = Type.GetType("System.Int32");
//設定方法中的引數值,如有多個引數可以追加多個
object[] paramsObj = new object[];
paramsObj[] = "parameter";
paramsObj[] = ;
paramsObj[] = ;
//執行方法
dynamic queryData = type.GetMethod($"{apiMethodsName}", paramsType)
.Invoke(instance, paramsObj);
//得到Result物件
object value = queryData.GetType()
.GetProperty("Result")
.GetValue(queryData);
//將資料序列化
var methodResult = JsonConvert.SerializeObject(queryData.Result);
writer.WriteLine("``` ");
//將資料寫入到文字中
writer.WriteLine($"{methodResult}");
writer.WriteLine("``` ");
#endregion #region 返回(響應)的引數欄位說明
writer.WriteLine("");
writer.WriteLine(" **返回引數說明** ");
writer.WriteLine("");
writer.WriteLine("|引數名|型別|說明|");
writer.WriteLine("|:----- |:-----|-----|");
//根據查詢到的Result物件獲取型別
Type messageModelType = Type.GetType(value.GetType().ToString());
//便利Result物件中的各個屬性資訊
foreach (var itemmessageModelProp in messageModelType.GetProperties())
{
//這個response物件裡面就是資料
if (itemmessageModelProp.Name.Equals("response"))
{
//根據value中的response屬性得到其型別
Type typeReturnData = Type.GetType(value.GetType().GetProperty("response").GetValue(value).ToString());
//遍歷response物件中的屬性
foreach (var item in typeReturnData.GetProperties())
{
//data中是實體物件
if (item.Name.Equals("data"))
{
//有可能是陣列,將中括號剔除掉
var dataType = item.PropertyType.ToString().Replace("[]", string.Empty);
Type propertyType = Type.GetType(dataType);//載入型別
foreach (PropertyInfo propertyInfo in propertyType.GetProperties())
{
if (propertyInfo.IsDefined(typeof(DescriptionAttribute), true))
{
//通過反射建立物件
DescriptionAttribute attribute = (DescriptionAttribute)propertyInfo.GetCustomAttribute(typeof(DescriptionAttribute), true);
writer.WriteLine($"|{propertyInfo.Name} |{propertyInfo.PropertyType} |{attribute._details} |");
}
}
}
else
{
//拿到與data物件平級的引數,看有沒有DescriptionAttribute特性
if (item.IsDefined(typeof(DescriptionAttribute), true))
{
//通過反射建立物件
DescriptionAttribute attribute = (DescriptionAttribute)item.GetCustomAttribute(typeof(DescriptionAttribute), true);
writer.WriteLine($"|{item.Name} |{item.PropertyType} |{attribute._details} |");
}
}
}
}
else
{
//拿到與response物件平級的引數,看有沒有DescriptionAttribute特性
if (itemmessageModelProp.IsDefined(typeof(DescriptionAttribute), true))
{
//通過反射建立物件
DescriptionAttribute attribute = (DescriptionAttribute)itemmessageModelProp.GetCustomAttribute(typeof(DescriptionAttribute), true);
writer.WriteLine($"|{itemmessageModelProp.Name} |{itemmessageModelProp.PropertyType} |{attribute._details} |");
}
}
}
#endregion #region 錯誤資訊一般也是定好的
writer.WriteLine(" **錯誤描述** ");
writer.WriteLine(" **(錯誤)返回示例**");
writer.WriteLine("");
writer.WriteLine("``` ");
writer.WriteLine(" {");
writer.WriteLine($" {"msg"}: {"伺服器異常"},");
writer.WriteLine($" {"success"}: {true},");
writer.WriteLine($" {"exception"}:{""},");
writer.WriteLine($" {"code"}: {500}");
writer.WriteLine(@" }");
writer.WriteLine($"```");
writer.WriteLine($" **(錯誤)返回引數說明** ");
writer.WriteLine($"");
writer.WriteLine($"|引數名|型別|說明|");
writer.WriteLine($"|:----- |:-----|-----|");
writer.WriteLine($"|msg |string |訊息 |");
writer.WriteLine($"|success |bool |操作是否成功 |");
writer.WriteLine($"|exception |string |具體的錯誤描述 |");
writer.WriteLine($"|code |string |狀態碼 |");
#endregion #region GC
writer.Close();//釋放記憶體
stream.Close();//釋放記憶體
#endregion #region 輸出檔案流
FileStream streamFile = new FileStream(webRootPath, FileMode.Open, FileAccess.Read);
return File(streamFile, "application/vnd.android.package-archive", webRootPath);
#endregion
}
catch (Exception ex)
{
writer.Close();//釋放記憶體
stream.Close();//釋放記憶體
throw new Exception(ex.Message);
}
}
}

會生成檔案流直接下載即可、

可還行?後續我會抽時間再補充修改,各位同學也可自行擴充套件、