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.

274 lines
9.7 KiB

4 years ago
4 years ago
4 years ago
4 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.DeclaredOnly | 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. if (!method.DeclaringType.Equals(application.Type)) return null;
  170. // 检查 ApiAttribute 特性。
  171. var apis = method.GetCustomAttributes(typeof(ApiAttribute), false);
  172. var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
  173. // Entry
  174. var entry = new ApiFunction();
  175. entry.Application = application;
  176. entry.Method = method;
  177. // 返回值。
  178. var returnable = method.ReturnType;
  179. if (returnable.Equals(typeof(void))) returnable = null;
  180. entry.Returnable = returnable;
  181. // 参数。
  182. var pis = method.GetParameters();
  183. if (pis != null && pis.Length > 0)
  184. {
  185. var pisc = pis.Length;
  186. for (var i = 0; i < pisc; i++)
  187. {
  188. var pi = pis[i];
  189. if (pi.IsIn) return null;
  190. if (pi.IsOut) return null;
  191. }
  192. entry.Parameters = pis;
  193. if (pisc == 1)
  194. {
  195. var pi = pis[0];
  196. var pt = pi.ParameterType;
  197. if (RuntimeUtility.IsInherits(pt, typeof(Source.Record))) entry.ParamIsRecord = true;
  198. }
  199. }
  200. if (api != null)
  201. {
  202. entry.Name = string.IsNullOrEmpty(api.Name) ? method.Name : api.Name;
  203. entry.Lower = entry.Name.ToLower();
  204. entry.Caption = api.Caption;
  205. entry.Description = api.Description;
  206. }
  207. else
  208. {
  209. entry.Name = method.Name;
  210. entry.Lower = entry.Name.ToLower();
  211. entry.Caption = null;
  212. entry.Description = null;
  213. }
  214. entry.Name = method.Name;
  215. entry.Lower = entry.Name.ToLower();
  216. // Caption
  217. var captions = method.GetCustomAttributes(typeof(CaptionAttribute), true);
  218. if (captions.Length > 0)
  219. {
  220. var caption = (CaptionAttribute)captions[0];
  221. entry.Caption = caption.Title;
  222. entry.Description = caption.Description;
  223. }
  224. // Hidden
  225. entry.Hidden = application.Hidden;
  226. if (!entry.Hidden && method.Contains<HiddenAttribute>(false)) entry.Hidden = true;
  227. return entry;
  228. }
  229. #endregion
  230. }
  231. }