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 _routers; private static IIocManager _iocManager; public static void Init( IReadOnlyDictionary 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(); 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(); 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; } } } } }