DynamicApiConvention.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. using System;
  2. using System.Linq;
  3. using System.Reflection;
  4. using System.Text.RegularExpressions;
  5. using System.Threading.Tasks;
  6. using Microsoft.AspNetCore.Mvc;
  7. using Microsoft.AspNetCore.Mvc.ActionConstraints;
  8. using Microsoft.AspNetCore.Mvc.ApplicationModels;
  9. using Microsoft.AspNetCore.Mvc.ModelBinding;
  10. using ZhonTai.DynamicApi.Attributes;
  11. using ZhonTai.DynamicApi.Enums;
  12. using ZhonTai.DynamicApi.Helpers;
  13. namespace ZhonTai.DynamicApi;
  14. public class DynamicApiConvention : IApplicationModelConvention
  15. {
  16. private readonly ISelectController _selectController;
  17. private readonly IActionRouteFactory _actionRouteFactory;
  18. public DynamicApiConvention(ISelectController selectController, IActionRouteFactory actionRouteFactory)
  19. {
  20. _selectController = selectController;
  21. _actionRouteFactory = actionRouteFactory;
  22. }
  23. public string GetSeparateWords(string value, NamingConventionEnum namingConvention = NamingConventionEnum.KebabCase)
  24. {
  25. if (string.IsNullOrWhiteSpace(value))
  26. return value;
  27. var separator = "-";
  28. if (namingConvention == NamingConventionEnum.SnakeCase)
  29. {
  30. separator = "_";
  31. }
  32. else if (namingConvention == NamingConventionEnum.ExtensionCase)
  33. {
  34. separator = ".";
  35. }
  36. return Regex.Replace(
  37. value,
  38. "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
  39. $"{separator}$1",
  40. RegexOptions.Compiled)
  41. .Trim()
  42. .ToLower();
  43. }
  44. public string GetFormatName(string value, NamingConventionEnum namingConvention = NamingConventionEnum.KebabCase)
  45. {
  46. if (namingConvention == NamingConventionEnum.KebabCase ||
  47. namingConvention == NamingConventionEnum.SnakeCase ||
  48. namingConvention == NamingConventionEnum.ExtensionCase)
  49. {
  50. return GetSeparateWords(value, namingConvention);
  51. }
  52. else if (namingConvention == NamingConventionEnum.PascalCase)
  53. {
  54. return value.FirstCharToUpper();
  55. }
  56. else if (namingConvention == NamingConventionEnum.CamelCase)
  57. {
  58. return value.FirstCharToLower();
  59. }
  60. return value;
  61. }
  62. public void Apply(ApplicationModel application)
  63. {
  64. foreach (var controller in application.Controllers)
  65. {
  66. var type = controller.ControllerType.AsType();
  67. var DynamicApiAttr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<DynamicApiAttribute>(type.GetTypeInfo());
  68. if (!(_selectController is DefaultSelectController) && _selectController.IsController(type))
  69. {
  70. controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray());
  71. if (AppConsts.NamingConvention == NamingConventionEnum.Custom)
  72. {
  73. controller.ControllerName = GetRestFulControllerName(controller.ControllerName);
  74. }
  75. else
  76. {
  77. controller.ControllerName = GetFormatName(controller.ControllerName, AppConsts.NamingConvention);
  78. }
  79. ConfigureDynamicApi(controller, DynamicApiAttr);
  80. }
  81. else
  82. {
  83. if (typeof(IDynamicApi).GetTypeInfo().IsAssignableFrom(type))
  84. {
  85. controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray());
  86. if (AppConsts.NamingConvention == NamingConventionEnum.Custom)
  87. {
  88. controller.ControllerName = GetRestFulControllerName(controller.ControllerName);
  89. }
  90. else
  91. {
  92. controller.ControllerName = GetFormatName(controller.ControllerName, AppConsts.NamingConvention);
  93. }
  94. ConfigureArea(controller, DynamicApiAttr);
  95. ConfigureDynamicApi(controller, DynamicApiAttr);
  96. }
  97. else
  98. {
  99. if (DynamicApiAttr != null)
  100. {
  101. ConfigureArea(controller, DynamicApiAttr);
  102. ConfigureDynamicApi(controller, DynamicApiAttr);
  103. }
  104. }
  105. }
  106. }
  107. }
  108. private void ConfigureArea(ControllerModel controller, DynamicApiAttribute attr)
  109. {
  110. if (!controller.RouteValues.ContainsKey("area"))
  111. {
  112. if (attr == null)
  113. {
  114. throw new ArgumentException(nameof(attr));
  115. }
  116. if (!string.IsNullOrEmpty(attr.Area))
  117. {
  118. controller.RouteValues["area"] = attr.Area;
  119. }
  120. else if (!string.IsNullOrEmpty(AppConsts.DefaultAreaName))
  121. {
  122. controller.RouteValues["area"] = AppConsts.DefaultAreaName;
  123. }
  124. }
  125. }
  126. private void ConfigureDynamicApi(ControllerModel controller, DynamicApiAttribute controllerAttr)
  127. {
  128. ConfigureApiExplorer(controller);
  129. ConfigureSelector(controller, controllerAttr);
  130. ConfigureParameters(controller);
  131. if (AppConsts.FormatResult)
  132. {
  133. ConfigureFormatResult(controller);
  134. }
  135. }
  136. private void ConfigureFormatResult(ControllerModel controller)
  137. {
  138. foreach (var action in controller.Actions)
  139. {
  140. if (!CheckNoMapMethod(action))
  141. {
  142. var returnType = action.ActionMethod.GetReturnType();
  143. if (returnType == typeof(void)) return;
  144. action.Filters.Add(new FormatResultAttribute(returnType));
  145. }
  146. }
  147. }
  148. private void ConfigureParameters(ControllerModel controller)
  149. {
  150. foreach (var action in controller.Actions)
  151. {
  152. if (!CheckNoMapMethod(action))
  153. foreach (var para in action.Parameters)
  154. {
  155. if (para.BindingInfo != null)
  156. {
  157. continue;
  158. }
  159. if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(para.ParameterInfo.ParameterType))
  160. {
  161. if (CanUseFormBodyBinding(action, para))
  162. {
  163. para.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
  164. }
  165. }
  166. }
  167. }
  168. }
  169. private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
  170. {
  171. if (AppConsts.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
  172. {
  173. return false;
  174. }
  175. foreach (var selector in action.Selectors)
  176. {
  177. if (selector.ActionConstraints == null)
  178. {
  179. continue;
  180. }
  181. foreach (var actionConstraint in selector.ActionConstraints)
  182. {
  183. var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint;
  184. if (httpMethodActionConstraint == null)
  185. {
  186. continue;
  187. }
  188. if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD")))
  189. {
  190. return false;
  191. }
  192. }
  193. }
  194. return true;
  195. }
  196. #region ConfigureApiExplorer
  197. private void ConfigureApiExplorer(ControllerModel controller)
  198. {
  199. if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
  200. {
  201. controller.ApiExplorer.GroupName = controller.ControllerName;
  202. }
  203. if (controller.ApiExplorer.IsVisible == null)
  204. {
  205. controller.ApiExplorer.IsVisible = true;
  206. }
  207. foreach (var action in controller.Actions)
  208. {
  209. if (!CheckNoMapMethod(action))
  210. ConfigureApiExplorer(action);
  211. }
  212. }
  213. private void ConfigureApiExplorer(ActionModel action)
  214. {
  215. if (action.ApiExplorer.IsVisible == null)
  216. {
  217. action.ApiExplorer.IsVisible = true;
  218. }
  219. }
  220. #endregion
  221. /// <summary>
  222. /// //不映射指定的方法
  223. /// </summary>
  224. /// <param name="action"></param>
  225. /// <returns></returns>
  226. private bool CheckNoMapMethod(ActionModel action)
  227. {
  228. bool isExist = false;
  229. var noMapMethod = ReflectionHelper.GetSingleAttributeOrDefault<NonDynamicMethodAttribute>(action.ActionMethod);
  230. if (noMapMethod != null)
  231. {
  232. action.ApiExplorer.IsVisible = false;//对应的Api不映射
  233. isExist = true;
  234. }
  235. return isExist;
  236. }
  237. private void ConfigureSelector(ControllerModel controller, DynamicApiAttribute controllerAttr)
  238. {
  239. if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
  240. {
  241. return;
  242. }
  243. var areaName = string.Empty;
  244. if (controllerAttr != null)
  245. {
  246. areaName = controllerAttr.Area;
  247. }
  248. foreach (var action in controller.Actions)
  249. {
  250. if (!CheckNoMapMethod(action))
  251. ConfigureSelector(areaName, controller.ControllerName, action);
  252. }
  253. }
  254. private void ConfigureSelector(string areaName, string controllerName, ActionModel action)
  255. {
  256. var nonAttr = ReflectionHelper.GetSingleAttributeOrDefault<NonDynamicApiAttribute>(action.ActionMethod);
  257. if (nonAttr != null)
  258. {
  259. return;
  260. }
  261. if (action.Selectors.IsNullOrEmpty() || action.Selectors.Any(a => a.ActionConstraints.IsNullOrEmpty()))
  262. {
  263. if (!CheckNoMapMethod(action))
  264. AddAppServiceSelector(areaName, controllerName, action);
  265. }
  266. else
  267. {
  268. NormalizeSelectorRoutes(areaName, controllerName, action);
  269. }
  270. }
  271. private void AddAppServiceSelector(string areaName, string controllerName, ActionModel action)
  272. {
  273. var verb = GetHttpVerb(action);
  274. if (AppConsts.NamingConvention == NamingConventionEnum.Custom)
  275. {
  276. action.ActionName = GetRestFulActionName(action.ActionName);
  277. }
  278. else
  279. {
  280. action.ActionName = GetFormatName(action.ActionName, AppConsts.NamingConvention);
  281. }
  282. var appServiceSelectorModel = action.Selectors[0];
  283. if (appServiceSelectorModel.AttributeRouteModel == null)
  284. {
  285. appServiceSelectorModel.AttributeRouteModel = CreateActionRouteModel(areaName, controllerName, action);
  286. }
  287. if (!appServiceSelectorModel.ActionConstraints.Any())
  288. {
  289. appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb }));
  290. switch (verb)
  291. {
  292. case "GET":
  293. appServiceSelectorModel.EndpointMetadata.Add(new HttpGetAttribute());
  294. break;
  295. case "POST":
  296. appServiceSelectorModel.EndpointMetadata.Add(new HttpPostAttribute());
  297. break;
  298. case "PUT":
  299. appServiceSelectorModel.EndpointMetadata.Add(new HttpPutAttribute());
  300. break;
  301. case "DELETE":
  302. appServiceSelectorModel.EndpointMetadata.Add(new HttpDeleteAttribute());
  303. break;
  304. default:
  305. throw new Exception($"Unsupported http verb: {verb}.");
  306. }
  307. }
  308. }
  309. /// <summary>
  310. /// Processing action name
  311. /// </summary>
  312. /// <param name="actionName"></param>
  313. /// <returns></returns>
  314. private static string GetRestFulActionName(string actionName)
  315. {
  316. // custom process action name
  317. var appConstsActionName = AppConsts.GetRestFulActionName?.Invoke(actionName);
  318. if (appConstsActionName != null)
  319. {
  320. return appConstsActionName;
  321. }
  322. // default process action name.
  323. // Remove Postfix
  324. actionName = actionName.RemovePostFix(AppConsts.ActionPostfixes.ToArray());
  325. // Remove Prefix
  326. var verbKey = actionName.GetPascalOrCamelCaseFirstWord().ToLower();
  327. if (AppConsts.HttpVerbs.ContainsKey(verbKey))
  328. {
  329. if (actionName.Length == verbKey.Length)
  330. {
  331. return "";
  332. }
  333. else
  334. {
  335. return actionName.Substring(verbKey.Length);
  336. }
  337. }
  338. else
  339. {
  340. return actionName;
  341. }
  342. }
  343. private static string GetRestFulControllerName(string controllerName)
  344. {
  345. // custom process action name
  346. var appConstsControllerName = AppConsts.GetRestFulControllerName?.Invoke(controllerName);
  347. if (appConstsControllerName != null)
  348. {
  349. return appConstsControllerName;
  350. }
  351. else
  352. {
  353. return controllerName;
  354. }
  355. }
  356. private void NormalizeSelectorRoutes(string areaName, string controllerName, ActionModel action)
  357. {
  358. if (AppConsts.NamingConvention == NamingConventionEnum.Custom)
  359. {
  360. action.ActionName = GetRestFulActionName(action.ActionName);
  361. }
  362. else
  363. {
  364. action.ActionName = GetFormatName(action.ActionName, AppConsts.NamingConvention);
  365. }
  366. foreach (var selector in action.Selectors)
  367. {
  368. selector.AttributeRouteModel = selector.AttributeRouteModel == null ?
  369. CreateActionRouteModel(areaName, controllerName, action) :
  370. AttributeRouteModel.CombineAttributeRouteModel(CreateActionRouteModel(areaName, controllerName, action), selector.AttributeRouteModel);
  371. }
  372. }
  373. private static string GetHttpVerb(ActionModel action)
  374. {
  375. var getValueSuccess = AppConsts.AssemblyDynamicApiOptions
  376. .TryGetValue(action.Controller.ControllerType.Assembly, out AssemblyDynamicApiOptions assemblyDynamicApiOptions);
  377. if (getValueSuccess && !string.IsNullOrWhiteSpace(assemblyDynamicApiOptions?.HttpVerb))
  378. {
  379. return assemblyDynamicApiOptions.HttpVerb;
  380. }
  381. var verbKey = action.ActionName.GetPascalOrCamelCaseFirstWord().ToLower();
  382. var verb = AppConsts.HttpVerbs.ContainsKey(verbKey) ? AppConsts.HttpVerbs[verbKey] : AppConsts.DefaultHttpVerb;
  383. return verb;
  384. }
  385. private AttributeRouteModel CreateActionRouteModel(string areaName, string controllerName, ActionModel action)
  386. {
  387. var route = _actionRouteFactory.CreateActionRouteModel(areaName, controllerName, action);
  388. return new AttributeRouteModel(new RouteAttribute(route));
  389. }
  390. }