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.

280 lines
9.9 KiB

4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Text;
  5. namespace Apewer.Web
  6. {
  7. /// <summary>入口集合。</summary>
  8. public sealed class ApiEntries
  9. {
  10. #region 实例。
  11. object locker = new object();
  12. Dictionary<string, ApiApplication> Applications = null;
  13. List<ApiApplication> Items = null;
  14. internal ApiApplication Get(string name)
  15. {
  16. if (string.IsNullOrEmpty(name)) return null;
  17. if (Applications == null) return null;
  18. var lower = name.ToLower();
  19. ApiApplication app;
  20. var found = false;
  21. lock (locker) { found = Applications.TryGetValue(lower, out app); }
  22. return found ? app : null;
  23. }
  24. internal List<ApiApplication> Enumerate() => Items;
  25. /// <summary>清空当前实例。</summary>
  26. public void Clear()
  27. {
  28. lock (locker)
  29. {
  30. Applications = new Dictionary<string, ApiApplication>();
  31. Items = new List<ApiApplication>();
  32. }
  33. }
  34. /// <summary>追加指定的集合,指定 replace 参数将替换当前实例中的同名的入口。</summary>
  35. public void Append(ApiEntries entries, bool replace = false)
  36. {
  37. if (entries == null) return;
  38. lock (locker)
  39. {
  40. var dict = Applications ?? new Dictionary<string, ApiApplication>();
  41. foreach (var app in entries.Applications)
  42. {
  43. var key = app.Key;
  44. if (dict.ContainsKey(key))
  45. {
  46. if (replace) dict[key] = app.Value;
  47. continue;
  48. }
  49. dict.Add(app.Key, app.Value);
  50. }
  51. var list = new List<ApiApplication>(dict.Values);
  52. list.Sort(new Comparison<ApiApplication>((a, b) => a.Lower.CompareTo(b.Lower)));
  53. Applications = dict;
  54. Items = list;
  55. }
  56. }
  57. #endregion
  58. #region 静态方法。
  59. /// <summary>从指定的程序集获取入口。</summary>
  60. public static ApiEntries From(Assembly assembly)
  61. {
  62. if (assembly == null) return null;
  63. var types = RuntimeUtility.GetTypes(assembly, false);
  64. var dict = new Dictionary<string, ApiApplication>();
  65. foreach (var type in types)
  66. {
  67. var app = Application(type, true);
  68. if (app == null) continue;
  69. var lower = app.Lower;
  70. if (dict.ContainsKey(lower)) continue;
  71. dict.Add(lower, app);
  72. var funcs = new Dictionary<string, ApiFunction>();
  73. var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public);
  74. foreach (var method in methods)
  75. {
  76. var func = Function(app, method);
  77. if (func == null) continue;
  78. if (funcs.ContainsKey(func.Lower)) continue;
  79. funcs.Add(func.Lower, func);
  80. }
  81. app.Functions = funcs;
  82. app.Items = new List<ApiFunction>(funcs.Values);
  83. app.Items.Sort(new Comparison<ApiFunction>((a, b) => a.Name.CompareTo(b.Name)));
  84. }
  85. var entries = new ApiEntries();
  86. entries.Applications = dict;
  87. var list = new List<ApiApplication>(dict.Values);
  88. list.Sort(new Comparison<ApiApplication>((a, b) => a.Lower.CompareTo(b.Lower)));
  89. entries.Items = list;
  90. return entries;
  91. }
  92. /// <summary>从多个程序集中获取入口。</summary>
  93. public static ApiEntries From(IEnumerable<Assembly> assemblies, bool replace = false)
  94. {
  95. if (assemblies == null) return null;
  96. var entries = new ApiEntries();
  97. foreach (var assembly in assemblies) entries.Append(From(assembly), replace);
  98. return entries;
  99. }
  100. /// <summary>从当前程序中获取入口。 </summary>
  101. public static ApiEntries Calling() => From(Assembly.GetCallingAssembly());
  102. /// <summary>从当前 AppDomain 中获取入口。</summary>
  103. public static ApiEntries AppDomain(bool replace = false) => From(System.AppDomain.CurrentDomain.GetAssemblies(), replace);
  104. static ApiApplication Application(Type type, bool requireAttribute)
  105. {
  106. if (type == null) return null;
  107. // 检查类型的属性。
  108. if (!type.IsClass) return null;
  109. if (type.IsAbstract) return null;
  110. if (type.IsGenericType) return null;
  111. if (type.GetGenericArguments().NotEmpty()) return null;
  112. if (!RuntimeUtility.CanNew(type)) return null;
  113. // 检查类型的特性。
  114. var apis = type.GetCustomAttributes(typeof(ApiAttribute), false);
  115. var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
  116. if (requireAttribute && api == null) return null;
  117. // 检查基类。
  118. if (!RuntimeUtility.IsInherits(type, typeof(ApiController))) return null;
  119. // Entry
  120. var entry = new ApiApplication();
  121. entry.Type = type;
  122. entry.Attribute = api;
  123. // Attribute
  124. if (api != null)
  125. {
  126. entry.Name = string.IsNullOrEmpty(api.Name) ? type.Name : api.Name;
  127. entry.Lower = entry.Name.ToLower();
  128. var name = api.Name;
  129. if (string.IsNullOrEmpty(name)) name = type.Name;
  130. entry.Caption = api.Caption;
  131. entry.Description = api.Description;
  132. }
  133. else
  134. {
  135. entry.Name = type.Name;
  136. entry.Lower = entry.Name.ToLower();
  137. entry.Caption = null;
  138. entry.Description = null;
  139. entry.Hidden = true;
  140. }
  141. // Caption
  142. if (string.IsNullOrEmpty(entry.Caption))
  143. {
  144. var captions = type.GetCustomAttributes(typeof(CaptionAttribute), true);
  145. if (captions.Length > 0)
  146. {
  147. var caption = (CaptionAttribute)captions[0];
  148. entry.Caption = caption.Title;
  149. entry.Description = caption.Description;
  150. }
  151. }
  152. // Hidden
  153. if (type.Contains<HiddenAttribute>(false)) entry.Hidden = true;
  154. // Independent
  155. if (type.Contains<IndependentAttribute>(false)) entry.Independent = true;
  156. // Module
  157. var assemblyName = type.Assembly.GetName();
  158. entry.Module = TextUtility.Join("-", assemblyName.Name, assemblyName.Version.ToString());
  159. return entry;
  160. }
  161. static ApiFunction Function(ApiApplication application, MethodInfo method)
  162. {
  163. if (application == null) return null;
  164. if (method == null) return null;
  165. // 滤除构造函数、抽象方法、泛型和非本类定义方法。
  166. if (method.IsConstructor) return null;
  167. if (method.IsAbstract) return null;
  168. if (method.GetGenericArguments().NotEmpty()) return null;
  169. // 滤除 get 和 set 访问器。
  170. var methodName = method.Name;
  171. if (methodName.StartsWith("get_") || methodName.StartsWith("set_")) return null;
  172. // 定义者。
  173. var declaring = method.DeclaringType;
  174. if (declaring.Equals(typeof(object))) return null;
  175. // 检查 ApiAttribute 特性。
  176. var apis = method.GetCustomAttributes(typeof(ApiAttribute), false);
  177. var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
  178. // Entry
  179. var entry = new ApiFunction();
  180. entry.Application = application;
  181. entry.Method = method;
  182. // 返回值。
  183. var returnable = method.ReturnType;
  184. if (returnable.Equals(typeof(void))) returnable = null;
  185. entry.Returnable = returnable;
  186. // 参数。
  187. var pis = method.GetParameters();
  188. if (pis != null && pis.Length > 0)
  189. {
  190. var pisc = pis.Length;
  191. for (var i = 0; i < pisc; i++)
  192. {
  193. var pi = pis[i];
  194. if (pi.IsIn) return null;
  195. if (pi.IsOut) return null;
  196. }
  197. entry.Parameters = pis;
  198. if (pisc == 1)
  199. {
  200. var pi = pis[0];
  201. var pt = pi.ParameterType;
  202. if (RuntimeUtility.IsInherits(pt, typeof(Source.Record))) entry.ParamIsRecord = true;
  203. }
  204. }
  205. if (api != null)
  206. {
  207. entry.Name = string.IsNullOrEmpty(api.Name) ? method.Name : api.Name;
  208. entry.Lower = entry.Name.ToLower();
  209. entry.Caption = api.Caption;
  210. entry.Description = api.Description;
  211. }
  212. else
  213. {
  214. entry.Name = method.Name;
  215. entry.Lower = entry.Name.ToLower();
  216. entry.Caption = null;
  217. entry.Description = null;
  218. }
  219. entry.Name = method.Name;
  220. entry.Lower = entry.Name.ToLower();
  221. // Caption
  222. var captions = method.GetCustomAttributes(typeof(CaptionAttribute), true);
  223. if (captions.Length > 0)
  224. {
  225. var caption = (CaptionAttribute)captions[0];
  226. entry.Caption = caption.Title;
  227. entry.Description = caption.Description;
  228. }
  229. // Hidden
  230. entry.Hidden = application.Hidden;
  231. if (!entry.Hidden && method.Contains<HiddenAttribute>(false)) entry.Hidden = true;
  232. return entry;
  233. }
  234. #endregion
  235. }
  236. }