ApiMiddleware.cs 7.4 KB

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