123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- using Microsoft.AspNetCore.Http;
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Threading.Tasks;
- using VCommon.Ioc;
- using VCommon.Json;
- using VCommon.Logging;
- using VCommon.Reflection;
- using VCommon.VApplication;
- using VCommon.VApplication.Auditing;
- using VCommon.VOpenApi.Docgen;
- namespace VCommon.VOpenApi.VAspNetCore
- {
- public class ApiMiddleware
- {
- private static string _basePath;
- private static bool _isDebuggingEnabled;
- private static string _openApiDoc;
- private static IReadOnlyDictionary<string, ApiBind> _routers;
- private static IIocManager _iocManager;
- 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)
- {
- _basePath = basePath;
- _isDebuggingEnabled = isDebuggingEnabled;
- _iocManager = iocManager ?? new IocManager();
- //生成路由和调用绑定
- //暂定路由规则(不区分大小写): /<资源>/<方法>
- var apiRoutes = new Dictionary<string, ApiBind>();
- foreach (var service in services)
- {
- var resource = service.Key;
- var interfaceType = service.Value;
- if (false == interfaceType.IsInterface)
- {
- Logger.Warn("ApiModule: Not an interface type registered", new { resource, interfaceType });
- continue;
- }
- var methods = interfaceType.GetPublicInstanceMethods();
- foreach (var method in methods)
- {
- var rawRoute = $"/{resource}/{method.Name}";
- var route = rawRoute.ToLower();
- if (apiRoutes.ContainsKey(route))
- {
- Logger.Warn("ApiModule: Api method name already exist", route);
- continue;
- }
- var parameterInfos = method.GetParameters();
- var inputParams = parameterInfos;
- if (1 < inputParams.Length)
- {
- Logger.Warn("ApiModule: Api method params more than 1", new { route, method });
- continue;
- }
- apiRoutes[route] = new ApiBind(method, resource, method.Name, interfaceType, rawRoute);
- }
- }
- _routers = apiRoutes;
- _openApiDoc = docGen
- ? DocGenerator.Generate(title, version, _basePath, _routers.Values)
- : VJsonSerializer.Serialize(new { docGen = false });
- }
- //--------------- instance on every query ----------------
- private readonly RequestDelegate _next;
- public ApiMiddleware(RequestDelegate next)
- {
- _next = next;
- }
- public async Task Invoke(HttpContext context)
- {
- var requestPath = context.Request.Path.Value?.ToLower();
- if (requestPath?.StartsWith(_basePath) != true)
- {
- await _next.Invoke(context);
- return;
- }
- var path = requestPath.Substring(_basePath.Length);
- if (path == "/") //根目录吐出文档
- {
- context.Response.ContentType = "application/json";
- await context.Response.WriteAsync(_openApiDoc);
- }
- else if (_routers.TryGetValue(path, out var invokeBind))
- {
- context.Response.ContentType = "application/json";
- //create child container
- using var child = _iocManager.CreateChildren();
- child.RegisterInstanceToContainer(new ServiceInvokeTiming());
- //put session dependency
- child.RegisterInstanceToContainer(context);
- //override session to child container from root
- child.RegisterManually<VAspNetCoreSession>();
- try
- {
- await invokeBind.Invoke(it => child.Resolve(it), context.Request.Body, context.Response.Body);
- }
- catch (Exception exception)
- {
- await ErrorHandle(context, exception);
- }
- }
- else
- {
- context.Response.StatusCode = 404;
- context.Response.ContentType = "text/plain";
- await context.Response.WriteAsync("404 Not found");
- }
- }
- protected virtual async Task ErrorHandle(HttpContext context, Exception error)
- {
- //400:请求参数格式错误 VApiArgumentException
- //401:未授权(未登录) VApplicationAuthException 1
- //403:拒绝访问(已登录但没有权限) VApplicationAuthException 2
- //422:模型检查失败 VApplicationModelValidationException
- //500:服务器内部错误 *其他
- context.Response.ContentType = "application/json";
- switch (error)
- {
- case VFriendlyException friendlyException:
- context.Response.StatusCode = friendlyException.StatusCode;
- await context.Response.WriteAsync(VJsonSerializer.Serialize(new { friendlyException.Message }));
- break;
- case VApiArgumentException apiArgumentException:
- context.Response.StatusCode = 400;
- await context.Response.WriteAsync(VJsonSerializer.Serialize(new { apiArgumentException.Message }));
- break;
- case VApplicationModelValidationException modelValidationException:
- context.Response.StatusCode = 422;
- await context.Response.WriteAsync(VJsonSerializer.Serialize(new { modelValidationException.Message, modelValidationException.Results }));
- break;
- case VApplicationAuthException authException:
- context.Response.StatusCode = authException.Reason == AuthReason.AuthRequired ? 401 : 403;
- await context.Response.WriteAsync(VJsonSerializer.Serialize(new { authException.Message }));
- break;
- default:
- {
- context.Response.StatusCode = 500;
- string response;
- if (false == _isDebuggingEnabled)
- {
- response = VJsonSerializer.Serialize(new { Message = "Server internal error" });
- }
- else
- {
- try
- {
- response = VJsonSerializer.Serialize(error);
- }
- catch (Exception exception)
- {
- Logger.Error("ApiMiddleware:Exception json serializer fail", exception);
- response = VJsonSerializer.Serialize(new { Message = error.ToString() });
- }
- }
- await context.Response.WriteAsync(response);
- break;
- }
- }
- }
- }
- }
|