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.
240 lines
8.9 KiB
240 lines
8.9 KiB
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
|
|
|
|
}
|
|
|
|
}
|
|
|