ApiMiddleware.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. using Microsoft.AspNetCore.Http;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Reflection;
  5. using System.Threading.Tasks;
  6. using VCommon.Ioc;
  7. using VCommon.Json;
  8. using VCommon.Logging;
  9. using VCommon.Reflection;
  10. using VCommon.VApplication;
  11. using VCommon.VApplication.Auditing;
  12. using VCommon.VOpenApi.Docgen;
  13. namespace VCommon.VOpenApi.VAspNetCore
  14. {
  15. public class ApiMiddleware
  16. {
  17. private static string _basePath;
  18. private static bool _isDebuggingEnabled;
  19. private static string _openApiDoc;
  20. private static IReadOnlyDictionary<string, ApiBind> _routers;
  21. private static IIocManager _iocManager;
  22. public static void Init(
  23. IReadOnlyDictionary<string, Type> services, string basePath = "/api",
  24. IIocManager iocManager = null, bool docGen = false, string title = "Api Set",
  25. string version = "1.0", bool isDebuggingEnabled = false)
  26. {
  27. _basePath = basePath;
  28. _isDebuggingEnabled = isDebuggingEnabled;
  29. _iocManager = iocManager ?? new IocManager();
  30. //生成路由和调用绑定
  31. //暂定路由规则(不区分大小写): /<资源>/<方法>
  32. var apiRoutes = new Dictionary<string, ApiBind>();
  33. foreach (var service in services)
  34. {
  35. var resource = service.Key;
  36. var interfaceType = service.Value;
  37. if (false == interfaceType.IsInterface)
  38. {
  39. Logger.Warn("ApiModule: Not an interface type registered", new { resource, interfaceType });
  40. continue;
  41. }
  42. var methods = interfaceType.GetPublicInstanceMethods();
  43. foreach (var method in methods)
  44. {
  45. var rawRoute = $"/{resource}/{method.Name}";
  46. var route = rawRoute.ToLower();
  47. if (apiRoutes.ContainsKey(route))
  48. {
  49. Logger.Warn("ApiModule: Api method name already exist", route);
  50. continue;
  51. }
  52. var parameterInfos = method.GetParameters();
  53. var inputParams = parameterInfos;
  54. if (1 < inputParams.Length)
  55. {
  56. Logger.Warn("ApiModule: Api method params more than 1", new { route, method });
  57. continue;
  58. }
  59. apiRoutes[route] = new ApiBind(method, resource, method.Name, interfaceType, rawRoute);
  60. }
  61. }
  62. _routers = apiRoutes;
  63. _openApiDoc = docGen
  64. ? DocGenerator.Generate(title, version, _basePath, _routers.Values)
  65. : VJsonSerializer.Serialize(new { docGen = false });
  66. }
  67. //--------------- instance on every query ----------------
  68. private readonly RequestDelegate _next;
  69. public ApiMiddleware(RequestDelegate next)
  70. {
  71. _next = next;
  72. }
  73. public async Task Invoke(HttpContext context)
  74. {
  75. var requestPath = context.Request.Path.Value?.ToLower();
  76. if (requestPath?.StartsWith(_basePath) != true)
  77. {
  78. await _next.Invoke(context);
  79. return;
  80. }
  81. var path = requestPath.Substring(_basePath.Length);
  82. if (path == "/") //根目录吐出文档
  83. {
  84. context.Response.ContentType = "application/json";
  85. await context.Response.WriteAsync(_openApiDoc);
  86. }
  87. else if (_routers.TryGetValue(path, out var invokeBind))
  88. {
  89. context.Response.ContentType = "application/json";
  90. //create child container
  91. using var child = _iocManager.CreateChildren();
  92. child.RegisterInstanceToContainer(new ServiceInvokeTiming());
  93. //put session dependency
  94. child.RegisterInstanceToContainer(context);
  95. //override session to child container from root
  96. child.RegisterManually<VAspNetCoreSession>();
  97. try
  98. {
  99. await invokeBind.Invoke(it => child.Resolve(it), context.Request.Body, context.Response.Body);
  100. }
  101. catch (Exception exception)
  102. {
  103. await ErrorHandle(context, exception);
  104. }
  105. }
  106. else
  107. {
  108. context.Response.StatusCode = 404;
  109. context.Response.ContentType = "text/plain";
  110. await context.Response.WriteAsync("404 Not found");
  111. }
  112. }
  113. protected virtual async Task ErrorHandle(HttpContext context, Exception error)
  114. {
  115. //400:请求参数格式错误 VApiArgumentException
  116. //401:未授权(未登录) VApplicationAuthException 1
  117. //403:拒绝访问(已登录但没有权限) VApplicationAuthException 2
  118. //422:模型检查失败 VApplicationModelValidationException
  119. //500:服务器内部错误 *其他
  120. context.Response.ContentType = "application/json";
  121. switch (error)
  122. {
  123. case VFriendlyException friendlyException:
  124. context.Response.StatusCode = friendlyException.StatusCode;
  125. await context.Response.WriteAsync(VJsonSerializer.Serialize(new { friendlyException.Message }));
  126. break;
  127. case VApiArgumentException apiArgumentException:
  128. context.Response.StatusCode = 400;
  129. await context.Response.WriteAsync(VJsonSerializer.Serialize(new { apiArgumentException.Message }));
  130. break;
  131. case VApplicationModelValidationException modelValidationException:
  132. context.Response.StatusCode = 422;
  133. await context.Response.WriteAsync(VJsonSerializer.Serialize(new { modelValidationException.Message, modelValidationException.Results }));
  134. break;
  135. case VApplicationAuthException authException:
  136. context.Response.StatusCode = authException.Reason == AuthReason.AuthRequired ? 401 : 403;
  137. await context.Response.WriteAsync(VJsonSerializer.Serialize(new { authException.Message }));
  138. break;
  139. default:
  140. {
  141. context.Response.StatusCode = 500;
  142. string response;
  143. if (false == _isDebuggingEnabled)
  144. {
  145. response = VJsonSerializer.Serialize(new { Message = "Server internal error" });
  146. }
  147. else
  148. {
  149. try
  150. {
  151. response = VJsonSerializer.Serialize(error);
  152. }
  153. catch (Exception exception)
  154. {
  155. Logger.Error("ApiMiddleware:Exception json serializer fail", exception);
  156. response = VJsonSerializer.Serialize(new { Message = error.ToString() });
  157. }
  158. }
  159. await context.Response.WriteAsync(response);
  160. break;
  161. }
  162. }
  163. }
  164. }
  165. }