Files
serein-flow/Library/Web/Router.cs

666 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library.Attributes;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Enum = System.Enum;
using Type = System.Type;
namespace Serein.Library.Web
{
public interface IRouter
{
bool RegisterController(Type controllerType);
Task ProcessingAsync(HttpListenerContext context);
}
/// <summary>
/// 路由注册与解析
/// </summary>
public class Router : IRouter
{
/// <summary>
/// 控制器实例对象的类型,每次调用都会重新实例化,[Url - ControllerType]
/// </summary>
private readonly ConcurrentDictionary<string, Type> _controllerTypes; // 存储控制器类型
/// <summary>
/// 用于存储路由信息,[GET|POST - [Url - Method]]
/// </summary>
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>> _routes;
[AutoInjection]
public ISereinIOC SereinIOC { get; set; } // 用于存储路由信息
// private readonly ILoggerService loggerService; // 用于存储路由信息
//private Type PostRequest;
public Router()
{
_routes = new ConcurrentDictionary<string, ConcurrentDictionary<string, MethodInfo>>(); // 初始化路由字典
_controllerTypes = new ConcurrentDictionary<string, Type>(); // 初始化控制器实例对象字典
foreach (API method in Enum.GetValues(typeof(API))) // 遍历 HTTP 枚举类型的所有值
{
_routes.TryAdd(method.ToString(), new ConcurrentDictionary<string, MethodInfo>()); // 初始化每种 HTTP 方法对应的路由字典
}
Type baseAttribute = typeof(AutoHostingAttribute);
Type baseController = typeof(ControllerBase);
// 获取当前程序集
Assembly assembly = Assembly.GetExecutingAssembly();
// 获取包含“Controller”名称的类型
var controllerTypes = assembly.GetTypes().Where(type => type.IsSubclassOf(baseController) // 控制器子类
&& type.IsDefined(baseAttribute) // 包含特性
&& type.Name.Contains("Controller"));
foreach (var controllerType in controllerTypes)
{
RegisterController(controllerType);
}
}
/// <summary>
/// 在外部调用API后解析路由调用对应的方法
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task ProcessingAsync(HttpListenerContext context)
{
var request = context.Request; // 获取请求对象
var response = context.Response; // 获取响应对象
var url = request.Url; // 获取请求的 URL
var httpMethod = request.HttpMethod; // 获取请求的 HTTP 方法
var template = request.Url.AbsolutePath.ToLower();
if (!_routes[httpMethod].TryGetValue(template, out MethodInfo method))
{
return;
}
var routeValues = GetUrlData(url); // 解析 URL 获取路由参数
ControllerBase controllerInstance = (ControllerBase)SereinIOC.Instantiate(_controllerTypes[template]);// 使用反射创建控制器实例
if (controllerInstance == null)
{
return; // 未找到控制器实例
}
controllerInstance.Url = url.AbsolutePath;
try
{
object result;
switch (httpMethod) // 根据请求的 HTTP 方法执行不同的操作
{
case "GET": // 如果是 GET 请求传入方法、控制器、url参数
// loggerService.Information(GetLog(template));
result = InvokeControllerMethodWithRouteValues(method, controllerInstance, routeValues);
break;
case "POST": // POST 请求传入方法、控制器、请求体内容url参数
var requestBody = await ReadRequestBodyAsync(request); // 读取请求体内容
controllerInstance.BobyData = requestBody;
var requestJObject = JObject.Parse(requestBody); //requestBody.FromJSON<JObject>();
// loggerService.Information(GetLog(template, requestBody));
result = InvokeControllerMethod(method, controllerInstance, requestJObject, routeValues);
break;
default:
result = null;
break;
}
Return(response, result); // 返回结果
}
catch (Exception ex)
{
response.StatusCode = (int)HttpStatusCode.NotFound; // 返回 404 错误
Return(response, ex.Message); // 返回结果
}
}
/// <summary>
/// 自动注册并实例化控制器类型
/// </summary>
/// <param name="controllerType"></param>
public bool RegisterController(Type controllerType) // 方法声明,用于注册并实例化控制器类型
{
if (!controllerType.IsClass || controllerType.IsAbstract) return false; // 如果不是类或者是抽象类,则直接返回
var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
var methods = controllerType.GetMethods().Where(m => m.GetCustomAttribute<WebApiAttribute>() != null).ToArray();
foreach (var method in methods) // 遍历控制器类型的所有方法
{
var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
{
var url = AddRoutesUrl(autoHostingAttribute, routeAttribute, controllerType, method);
Console.WriteLine(url);
if (url == null) continue;
_controllerTypes[url] = controllerType;
}
}
return true;
}
#region Get Post对应的方法
/// <summary>
/// GET请求的控制器方法
/// </summary>
private object InvokeControllerMethodWithRouteValues(MethodInfo method, object controllerInstance, Dictionary<string, string> routeValues)
{
object[] parameters = GetMethodParameters(method, routeValues);
return InvokeMethod(method, controllerInstance, parameters);
}
/// <summary>
/// GET请求调用控制器方法传入参数
/// </summary>
/// <param name="method">方法</param>
/// <param name="controllerInstance">控制器实例</param>
/// <param name="methodParameters">参数列表</param>
/// <returns></returns>
private object InvokeMethod(MethodInfo method, object controllerInstance, object[] methodParameters)
{
object result = null;
try
{
result = method?.Invoke(controllerInstance, methodParameters);
}
catch (ArgumentException ex)
{
string targetType = ExtractTargetTypeFromExceptionMessage(ex.Message);
// 如果方法调用失败
result = new
{
error = $"函数签名类型[{targetType}]不符合",
};
}
catch (JsonSerializationException ex)
{
// 查找类型信息开始的索引
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length;
// 查找类型信息结束的索引
int endIndex = ex.Message.IndexOf("'", startIndex);
// 提取类型信息
string typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return result; // 调用方法并返回结果
}
private readonly Dictionary<MethodInfo, ParameterInfo[]> methodParameterCache = new Dictionary<MethodInfo, ParameterInfo[]>();
/// <summary>
/// POST请求调用控制器方法传入参数
/// </summary>
public object InvokeControllerMethod(MethodInfo method, object controllerInstance, JObject requestData, Dictionary<string, string> routeValues)
{
if (requestData == null) return null;
ParameterInfo[] parameters;
object[] cachedMethodParameters;
if (!methodParameterCache.TryGetValue(method, out parameters))
{
parameters = method.GetParameters();
}
cachedMethodParameters = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
string paramName = parameters[i].Name;
bool isUrlData = parameters[i].GetCustomAttribute(typeof(UrlAttribute)) != null;
bool isBobyData = parameters[i].GetCustomAttribute(typeof(BobyAttribute)) != null;
if (isUrlData)
{
if (routeValues.ContainsKey(paramName))
{
cachedMethodParameters[i] = ConvertValue(routeValues[paramName], parameters[i].ParameterType);
}
else
{
cachedMethodParameters[i] = null;
}
}
else if (isBobyData)
{
cachedMethodParameters[i] = ConvertValue(requestData.ToString(), parameters[i].ParameterType);
}
else
{
if (requestData.ContainsKey(paramName))
{
var rd = requestData[paramName];
if (parameters[i].ParameterType == typeof(string))
{
cachedMethodParameters[i] = rd.ToString();
}
else if (parameters[i].ParameterType == typeof(bool))
{
cachedMethodParameters[i] = rd.ToBool();
}
else if (parameters[i].ParameterType == typeof(int))
{
cachedMethodParameters[i] = rd.ToInt();
}
else if (parameters[i].ParameterType == typeof(double))
{
cachedMethodParameters[i] = rd.ToDouble();
}
else
{
cachedMethodParameters[i] = ConvertValue(rd, parameters[i].ParameterType);
}
}
else
{
cachedMethodParameters[i] = null;
}
}
}
// 缓存方法和参数的映射
//methodParameterCache[method] = cachedMethodParameters;
// 调用方法
return method.Invoke(controllerInstance, cachedMethodParameters);
}
#endregion
#region
/// <summary>
/// 读取Body中的消息
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private async Task<string> ReadRequestBodyAsync(HttpListenerRequest request)
{
using (Stream stream = request.InputStream)
{
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
}
/// <summary>
/// 检查方法入参参数类型,返回对应的入参数组
/// </summary>
/// <param name="method"></param>
/// <param name="routeValues"></param>
/// <returns></returns>
private object[] GetMethodParameters(MethodInfo method, Dictionary<string, string> routeValues)
{
ParameterInfo[] methodParameters = method.GetParameters();
object[] parameters = new object[methodParameters.Length];
for (int i = 0; i < methodParameters.Length; i++)
{
string paramName = methodParameters[i].Name;
if (routeValues.ContainsKey(paramName))
{
parameters[i] = ConvertValue(routeValues[paramName], methodParameters[i].ParameterType);
}
else
{
parameters[i] = null;
}
}
return parameters;
}
/// <summary>
/// 转为对应的类型
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <returns></returns>
private object ConvertValue(object value, Type targetType)
{
try
{
return JsonConvert.DeserializeObject(value.ToString(), targetType);
}
catch (JsonReaderException ex)
{
return value;
}
catch (JsonSerializationException ex)
{
// 如果无法转为对应的JSON对象
int startIndex = ex.Message.IndexOf("to type '") + "to type '".Length; // 查找类型信息开始的索引
int endIndex = ex.Message.IndexOf("'", startIndex); // 查找类型信息结束的索引
var typeInfo = ex.Message.Substring(startIndex, endIndex - startIndex); // 提取出错类型信息,该怎么传出去?
return null;
}
catch // (Exception ex)
{
return value;
}
#pragma warning restore CS0168 // 声明了变量,但从未使用过
}
/// <summary>
/// 返回响应消息
/// </summary>
/// <param name="response"></param>
/// <param name="msg"></param>
private static void Return(HttpListenerResponse response, object msg)
{
string resultData;
if (response != null)
{
try
{
if (msg is JArray jArray)
{
resultData = jArray.ToString();
}
else if (msg is JObject jObject)
{
resultData = jObject.ToString();
}
else if (msg is IEnumerable ienumerable)
{
resultData = JArray.FromObject(ienumerable).ToString();
}
else if (msg is string tmpmsg)
{
resultData = tmpmsg;
}
else
{
// 否则将其序列化为JObject
resultData = JObject.FromObject(msg).ToString();
}
//
byte[] buffer = Encoding.UTF8.GetBytes(resultData);
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
catch (Exception ex)
{
// If serialization fails, use the original message's string representation
try
{
resultData = ex.ToString();
byte[] buffer = Encoding.UTF8.GetBytes(resultData);
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
catch (Exception ex1)
{
Console.WriteLine(ex1);
}
}
}
}
/// <summary>
/// 从方法中收集路由信息返回方法对应的url
/// </summary>
/// <param name="autoHostingAttribute">类的特性</param>
/// <param name="webAttribute">方法的特性</param>
/// <param name="controllerType">控制器类型</param>
/// <param name="method">方法信息</param>
/// <returns>方法对应的urk</returns>
private string AddRoutesUrl(AutoHostingAttribute autoHostingAttribute, WebApiAttribute webAttribute, Type controllerType, MethodInfo method)
{
string controllerName;
if (string.IsNullOrWhiteSpace(autoHostingAttribute.Url))
{
controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
}
else
{
controllerName = autoHostingAttribute.Url;
}
var httpMethod = webAttribute.Http; // 获取 HTTP 方法
var customUrl = webAttribute.Url; // 获取自定义 URL
string url;
if (webAttribute.IsUrl)
{
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
{
url = $"/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
}
else
{
customUrl = CleanUrl(customUrl);
url = $"/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL并构建新的 URL
}
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
}
else
{
if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
{
url = $"/{controllerName}".ToLower(); // 构建默认 URL
}
else
{
customUrl = CleanUrl(customUrl);
url = $"/{controllerName}/{customUrl}".ToLower();// 清理自定义 URL并构建新的 URL
}
_routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
}
return url;
}
/// <summary>
/// 修正方法特性中的URL格式
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private static string CleanUrl(string url)
{
while (url.Length > 0 && url[0] == '/') // 去除开头的斜杠
{
url = url.Substring(1);
}
while (url.Length > 0 && url[url.Length - 1] == '/') // 去除末尾的斜杠
{
url = url.Substring(0, url.Length - 1);
}
for (int i = 0; i < url.Length - 1; i++) // 去除连续的斜杠
{
if (url[i] == '/' && url[i + 1] == '/')
{
url = url.Remove(i, 1);
i--;
}
}
return url; // 返回清理后的 URL
}
/// <summary>
/// 方法声明,用于解析 URL 获取路由参数
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
private Dictionary<string, string> GetUrlData(Uri uri)
{
Dictionary<string, string> routeValues = new Dictionary<string, string>();
var pathParts = uri.ToString().Split('?'); // 拆分 URL获取路径部分
if (pathParts.Length > 1) // 如果包含查询字符串
{
//var queryParams = HttpUtility.ParseQueryString(pathParts[1]); // 解析查询字符串
//foreach (string key in queryParams) // 遍历查询字符串的键值对
//{
// if (key == null) continue;
// routeValues[key] = queryParams[key]; // 将键值对添加到路由参数字典中
//}
var parsedQuery = QueryStringParser.ParseQueryString(pathParts[1]);
foreach (var kvp in parsedQuery)
{
//Console.WriteLine($"{kvp.Key}: {kvp.Value}");
routeValues[kvp.Key] = kvp.Value; // 将键值对添加到路由参数字典中
}
}
return routeValues; // 返回路由参数字典
}
/// <summary>
/// 从控制器调用方法的异常中获取出出错类型的信息
/// </summary>
/// <param name="errorMessage"></param>
/// <returns></returns>
public static string ExtractTargetTypeFromExceptionMessage(string errorMessage)
{
string targetText = "为类型“";
int startIndex = errorMessage.IndexOf(targetText);
if (startIndex != -1)
{
startIndex += targetText.Length;
int endIndex = errorMessage.IndexOf("”", startIndex);
if (endIndex != -1)
{
return errorMessage.Substring(startIndex, endIndex - startIndex);
}
}
return null;
}
#endregion
}
internal static class WebFunc
{
public static bool ToBool(this JToken token,bool defult = false)
{
var value = token?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return defult;
}
if (!bool.TryParse(value, out bool result))
{
return defult;
}
else
{
return result;
}
}
public static int ToInt(this JToken token, int defult = 0)
{
var value = token?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return defult;
}
if (!int.TryParse(value, out int result))
{
return defult;
}
else
{
return result;
}
}
public static double ToDouble(this JToken token, double defult = 0)
{
var value = token?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return defult;
}
if (!int.TryParse(value, out int result))
{
return defult;
}
else
{
return result;
}
}
}
#region
// private readonly ConcurrentDictionary<string, bool> _controllerAutoHosting; // 存储是否实例化
// private readonly ConcurrentDictionary<string, object> _controllerInstances;
//public void CollectRoutes(Type controllerType)
//{
// string controllerName = controllerType.Name.Replace("Controller", "").ToLower(); // 获取控制器名称并转换为小写
// foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
// {
// var routeAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
// if (routeAttribute != null) // 如果存在 WebAPIAttribute 属性
// {
// var customUrl = routeAttribute.Url; // 获取自定义 URL
// string url;
// if (string.IsNullOrEmpty(customUrl)) // 如果自定义 URL 为空
// {
// url = $"/api/{controllerName}/{method.Name}".ToLower(); // 构建默认 URL
// }
// else
// {
// customUrl = CleanUrl(customUrl);
// url = $"/api/{controllerName}/{method.Name}/{customUrl}".ToLower();// 清理自定义 URL并构建新的 URL
// }
// var httpMethod = routeAttribute.Http; // 获取 HTTP 方法
// _routes[httpMethod.ToString()].TryAdd(url, method); // 将 URL 和方法添加到对应的路由字典中
// }
// }
//}
//public void RegisterRoute<T>(T controllerInstance) // 方法声明,用于动态注册路由
//{
// Type controllerType = controllerInstance.GetType(); // 获取控制器实例的类型
// var autoHostingAttribute = controllerType.GetCustomAttribute<AutoHostingAttribute>();
// foreach (var method in controllerType.GetMethods()) // 遍历控制器类型的所有方法
// {
// var webAttribute = method.GetCustomAttribute<WebApiAttribute>(); // 获取方法上的 WebAPIAttribute 自定义属性
// if (webAttribute != null) // 如果存在 WebAPIAttribute 属性
// {
// var url = AddRoutesUrl(autoHostingAttribute, webAttribute, controllerType, method);
// if (url == null) continue;
// _controllerInstances[url] = controllerInstance;
// _controllerAutoHosting[url] = false;
// }
// }
//}
#endregion
}