我目前每天主要工作以开发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);
}
}
}

会生成文件流直接下载即可、

可还行?后续我会抽时间再补充修改,各位同学也可自行扩展、

C#根据反射动态创建ShowDoc接口文本信息的更多相关文章

  1. StructureMap.dll 中的 GetInstance 重载 + 如何利用 反射动态创建泛型类

    public static T GetInstance<T>(ExplicitArguments args); // // Summary: // Creates a new instan ...

  2. C#反射动态创建实例并调用方法

    在.Net 中,程序集(Assembly)中保存了元数据(MetaData)信息,因此就可以通过分析元数据来获取程序集中的内容,比如类,方法,属性等,这大大方便了在运行时去动态创建实例. MSDN解释 ...

  3. .Net 中的反射(动态创建类型实例) - Part.4

    动态创建对象 在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它.可以说,前面三节,我们学习的都是反射是什么,在接下来的章节中,我们将学习反射可以 ...

  4. .Net 中的反射(动态创建类型实例)

    动态创建对象 在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它.可以说,前面三节,我们学习的都是反射是什么,在接下来的章节中,我们将学习反射可以 ...

  5. MFC小程序003------MFC使用WebBrowser组件,在对话框中创建滚动视图,动态创建一个静态文本控件并设置鼠标单击的消息响应

    MFC小程序截图: 一.在MFC中简单使用WebBrowser的ActiveX插件的方法: 见博文:  http://blog.csdn.net/supermanking/article/detail ...

  6. 反射动态创建不同的Processor

    1. 定义抽象方法 public abstract class BaseProcesser    {        public abstract void GetCustomerReportCard ...

  7. OC 反射-->动态创建类

    系统方法 NSLog(@"%s", __func__); //打印出类的方法名称,如: //打印结果:2018-02-22 10:52:15.394575+0800 DemoRun ...

  8. C# 学习笔记(一) Winform利用Assembly反射动态创建窗体

    1. 添加Reflection //添加对Reflection程序集引用 using System.Reflection; // 引用窗体创建方法CreateForm,传入参数 private voi ...

  9. WCF 动态调用(动态创建实例接口)

    很多时候,服务地址都不止一个的,这个时候就要动态去配置地址.配置Web.config,很麻烦 下面就看看怎样实现动态调用WCF. 首先看看动态创建服务对象的代码: using System; usin ...

随机推荐

  1. c常用函数-strcat 和 strncat

    strcat 和 strncat strcat与strncat都是字符串连接函数,功能上稍有区别: strcat可以把一个字符串的全部内容复制到另一个字符串的后面; strncat则是把一个字符串的指 ...

  2. android中获取其他应用的SharedPreferences

    在android中获取其他应用的SharedPreferences,需要其他应用设置的Mode为MODE_WORLD_READABLE或者MODE_WORLD_WRITABLE两种模式.要获取其他应用 ...

  3. [问题解决]Windows下python中pydoc命令提示“'pydoc' 不是内部或外部命令,也不是可运行的程序 或批处理文件。”

    解决方法:python -m pydoc 例:python -m pydoc print

  4. Optional 容器类

    什么是Optional容器类 Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的 ...

  5. 5种经典的Linux桌面系统

    最近一直在准备Linux相关的PPT,对于一个老码农来说Linux系统自然是比较熟悉了,随口可以说出好几种Linux的版本,然而对于计算机初学者可能就知道windows操作系统.也许你告诉他你可以安装 ...

  6. JavaWeb网上图书商城完整项目-CommonUtils(1生成uuid,2Map转换成JavaBean)

    java工程中添加上面的jar包 CommonUtils类就两个方法: l  String uuid():生成长度32的随机字符,通常用来做实体类的ID.底层使用了UUID类完成: l  T toBe ...

  7. Spring7——开发基于注解形式的spring

    开发基于注解形式的spring SpringIOC容器的2种形式: (1)xml配置文件:applicationContext.xml; 存bean:<bean> 取bean: Appli ...

  8. opencv+python实现图像锐化

    突然发现网上都是些太繁琐的方法,我就找opencv锐化函数咋这么墨迹. 直接上代码: kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], ...

  9. app自动化测试环境配置:adb环境配置、monkey环境配置、appium环境配置大全

    1. 安装jdk 2. 安装配置Andriod sdk 安装Andriod sdk前首先需要安装配置好jdk环境. 然后安装Android sdk 安装完成后需要配置环境变量:ANDROID_HOME ...

  10. 基于 fetch 的请求封装

    原生 fetch 请求失败后(如无网络)状态会变成 reject 走 .catch .绝大多数情况下业务场景只需要给个 toast 等简单处理.每个请求都 .catch 会显得格外繁琐,并且如果不 . ...