我目前每天主要工作以开发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; };//1044/ <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\"}}
写好要查询的接口,待会儿生成这个接口的ad8接口文档信息
[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 ne15b0w 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 = _hostingEnvironmentad8.WebRootPath + @\"\\ApiInfo.txt\";//得到文件流FileStream stream = new FileStream(webRootPath, FileMode.Create, FileAccess.Write);//创建写入的文件流对象StreamWriter writer = new StreamWriter(stream);#endregiontry{#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)){15a8//实例化得到一个DescriptionAttribute类型//通过反射创建对象DescriptionAttribute attribute = (DescriptionAttribute)method.GetCustomAttribute(typeof(DescriptionAttribute), true);writer.WriteLine($\"{attribute._details}\");}elsewriter.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 参数,一般删除参数都为idwriter.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 |返回的消息 |\");#endregiongoto 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($\"**参数:** \");writerad8.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}|\");}elsewriter.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}#endregionelse{#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(\"\");writer3ff8.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 GCwriter.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);}}}
会生成文件流直接下载即可、
可还行?各位同学也可自行扩展,如有不足,请见谅!