C#根據反射動態創建ShowDoc接口文本信息


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

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

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

一般來說,分頁查詢的響應信息結構都是一樣的。不同的接口數據不同而已,所以返回的那個實體對象就反射那個對象。我是在屬性上標注特性以獲得相應注釋信息。

添加和修改在實體參數上標注特性,刪除就最簡單了(請看代碼)。至於返回值這些,一般都是提前定好的,個別接口不一樣的話,就手動改一下。

首先新建一個Api項目

 定義一個實體類和要返回的信息類。

public class Products
{
    [DescriptionAttribute("數據id")]
    public int id { get; set; }
    [DescriptionAttribute("商品名稱")]
    [Required(ErrorMessage = "商品名稱必傳")]
    public string productNams { get; set; }
    [DescriptionAttribute("商品價格")]
    [Required(ErrorMessage = "價格必傳")]
    public float price { get; set; }
}
/// <summary>
/// 通用返回信息類
/// </summary>
public class MessageModel<T> where T : class
{
    [DescriptionAttribute("狀態碼")]
    public int code { get; set; } = 200;
    /// <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除外),還有一個用來標注添加和修改的Model對象。

//類和類中的屬性信息用這個特性
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;
    }
}

//添加和修改的Model參數用這個特性
public class ModelParameterAttribute : Attribute { public Type _objectT; public ModelParameterAttribute(Type objectT) { this._objectT = objectT; } }

將要請求的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 = 1, int pageSize = 30)
    {
        var list = new List<Products>()
        {
            new Products{ id=1,productNams="商品1",price=13.6f},
            new Products{ id=2,productNams="商品2",price=14.6f},
            new Products{ id=3,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
            }
        };
    }

    [HttpPost]
    [DescriptionAttribute("添加商品數據")]
    public async Task<IActionResult> SaveDatas([FromBody] [ModelParameterAttribute(typeof(Products))]Products products)
    {
        return Json(new
        {
            success = true,
            msr = "操作成功",
            code = 200
        });
    }

    [HttpDelete]
    [DescriptionAttribute("刪除商品數據")]
    public async Task<IActionResult> DeleteDatas(int id)
    {
        return Json(new
        {
            success = true,
            msr = "操作成功",
            code = 200
        });
    }
}

再寫一個接口,專門用來查詢指定接口的信息。兩個參數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($"**請求方式:**");
                #endregion

                #region 根據抽象父類HttpMethodAttribute得到接口的請求類型
                if (method.IsDefined(typeof(HttpMethodAttribute), true))
                {
                    //通過反射創建對象
                    HttpMethodAttribute attribute = (HttpMethodAttribute)method.GetCustomAttribute(typeof(HttpMethodAttribute), true);
                    writer.WriteLine($"- {attribute.HttpMethods.ToArray()[0]}");
                    #region 刪除的接口
                    if (attribute.HttpMethods.ToArray()[0].ToString().Equals("DELETE"))
                    {
                        #region 參數,一般刪除參數都為id
                        writer.WriteLine("");
                        writer.WriteLine($"**參數:** ");
                        writer.WriteLine("");
                        writer.WriteLine($"|參數名|必選|類型|說明|");
                        writer.WriteLine($"|:----|:---|:-----|-----|");
                        writer.WriteLine($"|id |是  |int |數據id|");
                        #endregion

                        #region 返回示例,一般都是定好的
                        writer.WriteLine("");
                        writer.WriteLine(" **返回示例**");
                        writer.WriteLine("");
                        writer.WriteLine("```");
                        writer.WriteLine(" msg ='操作成功,'");
                        writer.WriteLine(" success = true,");
                        writer.WriteLine(" code = 200");
                        writer.WriteLine("```");
                        writer.WriteLine("");
                        #endregion

                        #region 返回參數說明,一般也是定好的
                        writer.WriteLine("");
                        writer.WriteLine($" **返回參數說明**  ");
                        writer.WriteLine("");
                        writer.WriteLine($"|參數名|類型|說明|");
                        writer.WriteLine($"|:-----|:-----|-----|");
                        writer.WriteLine($" |code|string|狀態碼|");
                        writer.WriteLine($" |success |bool|添加是否成功|");
                        writer.WriteLine($"|msg |string |返回的消息  |");
                        #endregion

                        goto action;
                    }
                    #endregion
                }
                #endregion

                #region 查看API方法參數是否有ModelParameterAttribute的特性,有的話就是添加和修改否則是查詢
                ParameterInfo modelParameter = method.GetParameters()[0];
                if (modelParameter.IsDefined(typeof(ModelParameterAttribute), true))
                {
                    #region 反射得到Model參數的各類信息
                    //實例化得到一個CustomAttribute類型
                    //通過反射創建對象
                    ModelParameterAttribute attribute = (ModelParameterAttribute)modelParameter.GetCustomAttribute(typeof(ModelParameterAttribute), true);
                    Type modelType = attribute._objectT;
                    writer.WriteLine("");
                    writer.WriteLine($"**參數:** ");
                    writer.WriteLine("");
                    writer.WriteLine($"|參數名|必選|類型|說明|");
                    writer.WriteLine("|:----|:---|:-----|-----|");
                    //遍歷Model中的屬性
                    foreach (var item in modelType.GetProperties())
                    {

                        bool isRequired = false;
                        //框架自帶的驗證特性RequiredAttribute=>字段上有就必填否則非必填
                        if (item.IsDefined(typeof(RequiredAttribute), true))
                            isRequired = true;
                        //Model的屬性字段上是否有DescriptionAttribute特性
                        if (item.IsDefined(typeof(DescriptionAttribute), true))
                        {
                            //創建實例=>得到詳情
                            DescriptionAttribute fieldAttribute = (DescriptionAttribute)item.GetCustomAttribute(typeof(DescriptionAttribute), true);
                            writer.WriteLine($"|{item.Name}| {(isRequired ? '是' : '否')} |{item.PropertyType}|{fieldAttribute._details}|");
                        }
                        else
                            writer.WriteLine($"|{item.Name}| {(isRequired ? '是' : '否')} |{item.PropertyType}|'字段說明數據(詳情)數據未添加'|");
                    }
                    #endregion

                    #region 返回示例,一般也是定好的
                    writer.WriteLine("");
                    writer.WriteLine($" **返回示例** ");
                    writer.WriteLine("");
                    writer.WriteLine($"```");
                    writer.WriteLine($" msg ='操作成功,'");
                    writer.WriteLine($" success = true,");
                    writer.WriteLine($" code = 200");
                    writer.WriteLine($"```");
                    #endregion

                    #region 返回參數說明,一般也是定好的
                    writer.WriteLine("");
                    writer.WriteLine($" **返回參數說明**  ");
                    writer.WriteLine("");
                    writer.WriteLine($"|參數名|類型|說明|");
                    writer.WriteLine($"|:-----|:-----|-----|");
                    writer.WriteLine($" |code|string|狀態碼|");
                    writer.WriteLine($" |success |bool|添加是否成功|");
                    writer.WriteLine($"|msg |string |返回的消息  |");
                    #endregion

                }
                #endregion
            else
            {
                    #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[3];
                    paramsType[0] = Type.GetType("System.String");
                    paramsType[1] = Type.GetType("System.Int32");
                    paramsType[2] = Type.GetType("System.Int32");
                    //設置方法中的參數值,如有多個參數可以追加多個   
                    object[] paramsObj = new object[3];
                    paramsObj[0] = "parameter";
                    paramsObj[1] = 1;
                    paramsObj[2] = 24;
                    //執行方法   
                    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

            }
        action:

                #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);
        }
    }
}

 會生成文件流直接下載即可、

 可還行?各位同學也可自行擴展,如有不足,請見諒!

Grinning Face on Microsoft Windows 10 May 2019 Update

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM