You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
9.3 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. using Apewer.Network;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Reflection;
  5. namespace Apewer.Web
  6. {
  7. /// <summary>API 行为。</summary>
  8. public sealed class ApiAction : IToJson
  9. {
  10. #region fields
  11. Type _type = null;
  12. MethodInfo _method = null;
  13. string _path = null;
  14. HttpMethod[] _methods = null;
  15. ApiParameter[] _parameters = null;
  16. UnparallelAttribute _unparallel = null;
  17. #endregion
  18. #region propeties
  19. /// <summary>控制器的反射类型。</summary>
  20. public Type Type { get => _type; }
  21. /// <summary>API 行为的反射方法。</summary>
  22. public MethodInfo MethodInfo { get => _method; }
  23. /// <summary>URL 路径。</summary>
  24. public string Path { get => _path; }
  25. /// <summary>HTTP 方法。</summary>
  26. public HttpMethod[] Methods
  27. {
  28. get
  29. {
  30. var result = new HttpMethod[_methods.Length];
  31. if (_methods.Length > 0) _methods.CopyTo(result, 0);
  32. return result;
  33. }
  34. }
  35. /// <summary>参数。</summary>
  36. public ApiParameter[] Parameters
  37. {
  38. get
  39. {
  40. var result = new ApiParameter[_parameters.Length];
  41. if (_parameters.Length > 0) _parameters.CopyTo(result, 0);
  42. return result;
  43. }
  44. }
  45. /// <summary>不可并行。</summary>
  46. public UnparallelAttribute Unparallel { get => _unparallel; }
  47. /// <summary>生成 JSON 实例。</summary>
  48. public Json ToJson() => ToJson(null);
  49. /// <summary>生成 JSON 实例。</summary>
  50. public Json ToJson(ApiActionJsonFormat format)
  51. {
  52. if (format == null) format = ApiActionJsonFormat.Default ?? new ApiActionJsonFormat();
  53. var methods = new Json();
  54. foreach (var method in _methods)
  55. {
  56. methods.AddItem(method.ToString().Lower());
  57. }
  58. var json = new Json();
  59. json.SetProperty("path", _path);
  60. json.SetProperty("methods", methods);
  61. if (format.WithReflection)
  62. {
  63. var reflection = new Json();
  64. reflection.SetProperty("type", _type.FullName);
  65. reflection.SetProperty("method", _method.Name);
  66. json.SetProperty("reflection", reflection);
  67. }
  68. if (format.WithParameters && _parameters.Length > 0)
  69. {
  70. var parameters = Json.NewArray();
  71. foreach (var parameter in _parameters)
  72. {
  73. parameters.AddItem(parameter.ToJson(format.WithReflection));
  74. }
  75. json.SetProperty("parameters", parameters);
  76. }
  77. return json;
  78. }
  79. /// <summary>生成字符串。</summary>
  80. public override string ToString() => _path;
  81. #endregion
  82. #region parse
  83. const string Separator = "/";
  84. /// <summary>创建 API 行为描述实例。</summary>
  85. /// <param name="type">控制器的反射类型。</param>
  86. /// <param name="method">API 行为的反射方法。</param>
  87. /// <param name="path">URL 路径。</param>
  88. /// <param name="methods">HTTP 方法。</param>
  89. /// <param name="parameters">参数。</param>
  90. /// <param name="unparallel">不可并行。</param>
  91. /// <exception cref="ArgumentNullException" />
  92. /// <exception cref="ArgumentException" />
  93. ApiAction(Type type, MethodInfo method, string path, HttpMethod[] methods, ApiParameter[] parameters, UnparallelAttribute unparallel)
  94. {
  95. if (type == null) throw new ArgumentNullException(nameof(type));
  96. if (method == null) throw new ArgumentNullException(nameof(method));
  97. if (method.IsAbstract) throw new ArgumentException($"参数 {nameof(method)} 是抽象的。");
  98. if (path.IsEmpty()) throw new ArgumentNullException(nameof(path));
  99. if (methods.IsEmpty()) throw new ArgumentNullException(nameof(methods));
  100. var split = path.Split('/');
  101. var segs = split.Trim();
  102. path = segs.Length < 1 ? Separator : (Separator + string.Join(Separator, segs));
  103. _type = type;
  104. _method = method;
  105. _path = path;
  106. _methods = methods;
  107. _parameters = parameters;
  108. _unparallel = unparallel;
  109. }
  110. /// <summary>解析控制器类型,获取 API 活动。</summary>
  111. /// <exception cref="ArgumentNullException" />
  112. public static ApiAction[] Parse(Type type)
  113. {
  114. if (type == null) throw new ArgumentNullException(nameof(type));
  115. if (type.FullName == "Front.Debug.Controller")
  116. {
  117. }
  118. // 检查类型的属性。
  119. if (!type.IsClass) return new ApiAction[0];
  120. if (type.IsAbstract) return new ApiAction[0];
  121. if (type.IsGenericType) return new ApiAction[0];
  122. if (type.GetGenericArguments().NotEmpty()) return new ApiAction[0];
  123. if (!RuntimeUtility.CanNew(type)) return new ApiAction[0];
  124. // 判断基类。
  125. if (!typeof(ApiController).IsAssignableFrom(type)) return new ApiAction[0];
  126. // 读取 URL 前缀。
  127. var prefixAttribute = RuntimeUtility.GetAttribute<RoutePrefixAttribute>(type, false);
  128. var prefixPath = (prefixAttribute == null || prefixAttribute.Path.IsEmpty()) ? null : prefixAttribute.Path.Split('/').Trim();
  129. // if (prefixPath.IsEmpty())
  130. // {
  131. // // 读取 API 特性。
  132. // var api = RuntimeUtility.GetAttribute<ApiAttribute>(type);
  133. // if (api != null)
  134. // {
  135. // var apiName = api.Name.ToTrim();
  136. // if (apiName.Lower().EndsWith("controller")) apiName = apiName.Substring(0, apiName.Length - 10);
  137. // if (apiName.NotEmpty()) prefixPath = new string[] { apiName };
  138. // }
  139. // }
  140. // 读取方法。
  141. var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
  142. var actions = new List<ApiAction>(methods.Length);
  143. foreach (var method in methods)
  144. {
  145. // 不支持构造函数和泛型。
  146. if (method.IsConstructor) continue;
  147. if (method.IsGenericMethod) continue;
  148. // 抽象类无法创建实例。
  149. if (method.IsAbstract) continue;
  150. if (!method.DeclaringType.Equals(type)) continue;
  151. // 必须有 Route 特性
  152. var route = RuntimeUtility.GetAttribute<RouteAttribute>(method);
  153. if (route == null) continue;
  154. // 确定路径
  155. var path = route.Path;
  156. if (path.IsEmpty())
  157. {
  158. if (prefixPath.IsEmpty()) continue;
  159. path = method.Name;
  160. }
  161. path = ConcatPath(prefixPath, path.Split('/').Trim());
  162. // 必须有 HTTP 方法
  163. var httpMethods = new List<HttpMethod>(9);
  164. if (RuntimeUtility.Contains<HttpConnectAttribute>(method)) httpMethods.Add(HttpMethod.CONNECT);
  165. if (RuntimeUtility.Contains<HttpDeleteAttribute>(method)) httpMethods.Add(HttpMethod.DELETE);
  166. if (RuntimeUtility.Contains<HttpGetAttribute>(method)) httpMethods.Add(HttpMethod.GET);
  167. if (RuntimeUtility.Contains<HttpHeadAttribute>(method)) httpMethods.Add(HttpMethod.HEAD);
  168. if (RuntimeUtility.Contains<HttpOptionsAttribute>(method)) httpMethods.Add(HttpMethod.OPTIONS);
  169. if (RuntimeUtility.Contains<HttpPatchAttribute>(method)) httpMethods.Add(HttpMethod.PATCH);
  170. if (RuntimeUtility.Contains<HttpPostAttribute>(method)) httpMethods.Add(HttpMethod.POST);
  171. if (RuntimeUtility.Contains<HttpPutAttribute>(method)) httpMethods.Add(HttpMethod.PUT);
  172. if (RuntimeUtility.Contains<HttpTraceAttribute>(method)) httpMethods.Add(HttpMethod.TRACE);
  173. if (httpMethods.Count < 1) continue;
  174. // 参数
  175. var parameters = new List<ApiParameter>();
  176. foreach (var pi in method.GetParameters())
  177. {
  178. var parameter = ApiParameter.Parse(pi);
  179. if (parameter == null) continue;
  180. parameters.Add(parameter);
  181. }
  182. // 不可并行
  183. var unparallel = RuntimeUtility.GetAttribute<UnparallelAttribute>(method);
  184. var action = new ApiAction(type, method, path, httpMethods.ToArray(), parameters.ToArray(), unparallel);
  185. actions.Add(action);
  186. }
  187. return actions.ToArray();
  188. }
  189. static string ConcatPath(string[] prefix, string[] path)
  190. {
  191. var list = new List<string>();
  192. if (prefix != null) list.AddRange(prefix);
  193. if (path != null) list.AddRange(path);
  194. var segs = list.Trim();
  195. if (segs.Length < 1) return Separator;
  196. var result = Separator + string.Join(Separator, segs);
  197. return result;
  198. }
  199. #endregion
  200. }
  201. }