You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
7.1 KiB

using Apewer.Web;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using static Apewer.NumberUtility;
using static Apewer.Web.ApiUtility;
namespace Apewer.AspNetBridge
{
/// <summary>桥接 ASP.NET 的控制器。</summary>
public class BridgeController : Web.ApiController
{
#region static routes
static RouteItem[] _routes = null;
/// <summary>导出所有路由项。</summary>
public static Json ExportRoutes()
{
var array = Json.NewArray();
if (_routes != null)
{
foreach (var route in _routes)
{
var item = route.ToJson();
array.AddItem(item);
}
}
return array;
}
/// <summary>初始化路由。</summary>
/// <param name="assemblies">包含控制器的程序集。</param>
/// <param name="withVoid">包含返回 System.Void 的方法。</param>
public static void Initialize(IEnumerable<Assembly> assemblies, bool withVoid = false) => _routes = RouteItem.Parse(assemblies, withVoid);
/// <summary>初始化路由,不包含返回 System.Void 的方法。</summary>
/// <param name="assemblies">包含控制器的程序集。</param>
public static void Initialize(params Assembly[] assemblies) => _routes = RouteItem.Parse(assemblies, false);
#endregion
#region events
/// <summary>发生异常时候的处理程序。</summary>
public static OnException OnException { get; set; }
/// <summary>输出异常。</summary>
/// <exception cref="ArgumentNullException"></exception>
public static void Output(ApiRequest request, ApiResponse response, MethodInfo method, Exception exception)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (response == null) throw new ArgumentNullException(nameof(response));
if (method == null) throw new ArgumentNullException(nameof(method));
if (exception == null) throw new ArgumentNullException(nameof(exception));
var sb = new StringBuilder();
if (request.IP.NotEmpty())
{
sb.Append(request.IP);
sb.Append(" ");
}
sb.Append(request.Method.ToString());
if (request.Url != null)
{
sb.Append(" ");
sb.Append(request.Url.OriginalString);
}
if (method != null)
{
sb.Append("\r\n");
sb.Append(method.DeclaringType.FullName);
sb.Append(".");
sb.Append(method.Name);
}
sb.Append("\r\n\r\n");
sb.Append(new ApiExceptionModel(exception).ToString());
var model = new ApiTextModel(sb.ToString());
model.Status = 500;
response.Model = model;
}
/// <summary>使用格式化程序处理所有响应。</summary>
/// <remarks>
/// <para>默认值:False( Bridge )</para>
/// <para>True:所有响应均使用自定义格式化程序,忽略 Bridge 内置程序。</para>
/// <para>False:优先使用 Bridge 内置程序格式化,对不支持的类型使用自定义程序。</para>
/// </remarks>
public static bool FormatAll { get; set; }
/// <summary>自定义格式化程序。</summary>
public static Func<object, ApiModel> Formatter { get; set; }
#endregion
#region instance
/// <summary></summary>
public BridgeController() : base((c) => ((BridgeController)c).Execute(), (c) => ((BridgeController)c).Default()) { }
bool Execute()
{
if (_routes == null) Initialize();
var routes = _routes;
var route = RouteItem.Match(routes, Request.Url.AbsolutePath, Request.Method);
try
{
Execute(route);
}
catch (Exception ex)
{
Logger.Write("Route Error", ex.InnerException.GetType().FullName, ex.InnerException.Message);
var action = OnException;
if (action != null) action.Invoke(Request, Response, route.Method, ex.InnerException);
else Response.Model = new ApiStatusModel(500);
}
return false;
}
void Default() { }
void Execute(RouteItem route)
{
if (route == null)
{
Response.Model = new ApiStatusModel(404);
return;
}
// 检查 HTTP 方法
switch (Request.Method)
{
case Network.HttpMethod.GET:
if (!route.Get)
{
Response.Model = new ApiStatusModel(405);
return;
}
break;
case Network.HttpMethod.POST:
if (!route.Post)
{
Response.Model = new ApiStatusModel(405);
return;
}
break;
default:
Response.Model = new ApiStatusModel(405);
return;
}
// 准备参数。
var pis = CollectionUtility.Vacuum(route.Parameters.Map(x => ApiParameter.Parse(x)));
var ps = ReadParameters(Request, route.Parameters);
// 准备控制器。
var c = Activator.CreateInstance(route.Controller) as ApiController;
c.Request = Request;
c.Response = Response;
// 调用 API 方法。
var r = route.Method.Invoke(c, ps);
// 格式化、返回。
if (route.Return == null || route.Return.Equals(typeof(void))) return;
Response.Model = Format(r) ?? new ApiStatusModel(204);
}
static ApiModel Format(object value)
{
var isNull = value.IsNull();
var all = FormatAll;
var formatter = Formatter;
var customized = formatter != null;
// 使用自定义格式化程序处理所有响应。
if (all && customized) return formatter.Invoke(value);
// 使用默认格式化。
if (value is ApiModel model) return model;
if (value is string text) return new ApiTextModel(text);
if (value is byte[] bytes) return new ApiBytesModel(bytes);
if (value is Json json) return new ApiJsonModel(json);
if (value is HttpActionResult har) return har.ToModel();
if (value is HttpResponseMessage hrm) return hrm.ToModel();
// 指定了自定义格式化程序。
if (customized) return formatter.Invoke(value);
// 默认。
return isNull ? null : new ApiTextModel(value.ToString());
}
#endregion
}
}