using Apewer.Network; using System; using System.Collections.Generic; using System.Reflection; using System.Text; namespace Apewer.Web { /// <summary>API 行为。</summary> public sealed class ApiAction : IToJson { #region fields Type _type = null; MethodInfo _method = null; string _path = null; HttpMethod[] _methods = null; ApiParameter[] _parameters = null; #endregion #region propeties /// <summary>控制器的反射类型。</summary> public Type Type { get => _type; } /// <summary>API 行为的反射方法。</summary> public MethodInfo MethodInfo { get => _method; } /// <summary>URL 路径。</summary> public string Path { get => _path; } /// <summary>HTTP 方法。</summary> public HttpMethod[] Methods { get { var result = new HttpMethod[_methods.Length]; if (_methods.Length > 0) _methods.CopyTo(result, 0); return result; } } /// <summary>参数。</summary> public ApiParameter[] Parameters { get { var result = new ApiParameter[_parameters.Length]; if (_parameters.Length > 0) _parameters.CopyTo(result, 0); return result; } } /// <summary>生成 JSON 实例。</summary> public Json ToJson() => ToJson(null); /// <summary>生成 JSON 实例。</summary> public Json ToJson(ApiActionJsonFormat format) { if (format == null) format = ApiActionJsonFormat.Default ?? new ApiActionJsonFormat(); var methods = new Json(); foreach (var method in _methods) { methods.AddItem(method.ToString().Lower()); } var json = new Json(); json.SetProperty("path", _path); json.SetProperty("methods", methods); if (format.WithReflection) { var reflection = new Json(); reflection.SetProperty("type", _type.FullName); reflection.SetProperty("method", _method.Name); json.SetProperty("reflection", reflection); } if (format.WithParameters && _parameters.Length > 0) { var parameters = Json.NewArray(); foreach (var parameter in _parameters) { parameters.AddItem(parameter.ToJson(format.WithReflection)); } json.SetProperty("parameters", parameters); } return json; } /// <summary>生成字符串。</summary> public override string ToString() => _path; #endregion #region parse const string Separator = "/"; /// <summary>创建 API 行为描述实例。</summary> /// <param name="type">控制器的反射类型。</param> /// <param name="method">API 行为的反射方法。</param> /// <param name="path">URL 路径。</param> /// <param name="methods">HTTP 方法。</param> /// <param name="parameters">参数。</param> /// <exception cref="ArgumentNullException" /> /// <exception cref="ArgumentException" /> ApiAction(Type type, MethodInfo method, string path, HttpMethod[] methods, ApiParameter[] parameters) { if (type == null) throw new ArgumentNullException(nameof(type)); if (method == null) throw new ArgumentNullException(nameof(method)); if (method.IsAbstract) throw new ArgumentException($"参数 {nameof(method)} 是抽象的。"); if (path.IsEmpty()) throw new ArgumentNullException(nameof(path)); if (methods.IsEmpty()) throw new ArgumentNullException(nameof(methods)); var split = path.Split('/'); var segs = split.Trim(); path = segs.Length < 1 ? Separator : (Separator + string.Join(Separator, segs)); _type = type; _method = method; _path = path; _methods = methods; _parameters = parameters; } /// <summary>解析控制器类型,获取 API 活动。</summary> /// <exception cref="ArgumentNullException" /> public static ApiAction[] Parse(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); if (type.FullName == "Front.Debug.Controller") { } // 检查类型的属性。 if (!type.IsClass) return new ApiAction[0]; if (type.IsAbstract) return new ApiAction[0]; if (type.IsGenericType) return new ApiAction[0]; if (type.GetGenericArguments().NotEmpty()) return new ApiAction[0]; if (!RuntimeUtility.CanNew(type)) return new ApiAction[0]; // 判断基类。 if (!typeof(ApiController).IsAssignableFrom(type)) return new ApiAction[0]; // 读取 URL 前缀。 var prefixAttribute = RuntimeUtility.GetAttribute<RoutePrefixAttribute>(type, false); var prefixPath = (prefixAttribute == null || prefixAttribute.Path.IsEmpty()) ? null : prefixAttribute.Path.Split('/').Trim(); // if (prefixPath.IsEmpty()) // { // // 读取 API 特性。 // var api = RuntimeUtility.GetAttribute<ApiAttribute>(type); // if (api != null) // { // var apiName = api.Name.ToTrim(); // if (apiName.Lower().EndsWith("controller")) apiName = apiName.Substring(0, apiName.Length - 10); // if (apiName.NotEmpty()) prefixPath = new string[] { apiName }; // } // } // 读取方法。 var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); var actions = new List<ApiAction>(methods.Length); foreach (var method in methods) { // 不支持构造函数和泛型。 if (method.IsConstructor) continue; if (method.IsGenericMethod) continue; // 抽象类无法创建实例。 if (method.IsAbstract) continue; if (!method.DeclaringType.Equals(type)) continue; // 必须有 Route 特性 var route = RuntimeUtility.GetAttribute<RouteAttribute>(method); if (route == null) continue; // 确定路径 var path = route.Path; if (path.IsEmpty()) { if (prefixPath.IsEmpty()) continue; path = method.Name; } path = ConcatPath(prefixPath, path.Split('/').Trim()); // 必须有 HTTP 方法 var httpMethods = new List<HttpMethod>(9); if (RuntimeUtility.Contains<HttpConnectAttribute>(method)) httpMethods.Add(HttpMethod.CONNECT); if (RuntimeUtility.Contains<HttpDeleteAttribute>(method)) httpMethods.Add(HttpMethod.DELETE); if (RuntimeUtility.Contains<HttpGetAttribute>(method)) httpMethods.Add(HttpMethod.GET); if (RuntimeUtility.Contains<HttpHeadAttribute>(method)) httpMethods.Add(HttpMethod.HEAD); if (RuntimeUtility.Contains<HttpOptionsAttribute>(method)) httpMethods.Add(HttpMethod.OPTIONS); if (RuntimeUtility.Contains<HttpPatchAttribute>(method)) httpMethods.Add(HttpMethod.PATCH); if (RuntimeUtility.Contains<HttpPostAttribute>(method)) httpMethods.Add(HttpMethod.POST); if (RuntimeUtility.Contains<HttpPutAttribute>(method)) httpMethods.Add(HttpMethod.PUT); if (RuntimeUtility.Contains<HttpTraceAttribute>(method)) httpMethods.Add(HttpMethod.TRACE); if (httpMethods.Count < 1) continue; // 参数 var parameters = new List<ApiParameter>(); foreach (var pi in method.GetParameters()) { var parameter = ApiParameter.Parse(pi); if (parameter == null) continue; parameters.Add(parameter); } var action = new ApiAction(type, method, path, httpMethods.ToArray(), parameters.ToArray()); actions.Add(action); } return actions.ToArray(); } static string ConcatPath(string[] prefix, string[] path) { var list = new List<string>(); if (prefix != null) list.AddRange(prefix); if (path != null) list.AddRange(path); var segs = list.Trim(); if (segs.Length < 1) return Separator; var result = Separator + string.Join(Separator, segs); return result; } #endregion } }