Browse Source

Apewer-6.8.0

dev
王厅 8 months ago
parent
commit
95b73314eb
  1. 1
      Apewer.Web/AspNetBridge/BridgeController.cs
  2. 18
      Apewer.Web/Web/AspNetCoreProvider.cs
  3. 4
      Apewer.Windows/Internals/Interop/Constant.cs
  4. 16
      Apewer.Windows/Internals/Interop/Kernel32.cs
  5. 157
      Apewer.Windows/WindowsUtility.cs
  6. 2
      Apewer/Apewer.props
  7. 81
      Apewer/Class.cs
  8. 177
      Apewer/ClockUtility.cs
  9. 67
      Apewer/CollectionUtility.cs
  10. 35
      Apewer/DateTimePart.cs
  11. 224
      Apewer/Json.cs
  12. 52
      Apewer/Network/HttpBody.cs
  13. 47
      Apewer/Network/HttpHeader.cs
  14. 369
      Apewer/Network/HttpHeaders.cs
  15. 18
      Apewer/Network/HttpMethod.cs
  16. 28
      Apewer/RuntimeUtility.cs
  17. 24
      Apewer/Web/ActionResult.cs
  18. 240
      Apewer/Web/ApiAction.cs
  19. 33
      Apewer/Web/ApiActionJsonFormat.cs
  20. 177
      Apewer/Web/ApiApplication.cs
  21. 15
      Apewer/Web/ApiCatch.cs
  22. 8
      Apewer/Web/ApiContext.cs
  23. 333
      Apewer/Web/ApiEntries.cs
  24. 200
      Apewer/Web/ApiEntry.cs
  25. 170
      Apewer/Web/ApiFunction.cs
  26. 16
      Apewer/Web/ApiInvoker.cs
  27. 18
      Apewer/Web/ApiMiddleware.cs
  28. 233
      Apewer/Web/ApiModel.cs
  29. 16
      Apewer/Web/ApiOptions.cs
  30. 77
      Apewer/Web/ApiParameter.cs
  31. 512
      Apewer/Web/ApiProcessor.cs
  32. 11
      Apewer/Web/ApiProvider.cs
  33. 5
      Apewer/Web/ApiRequest.cs
  34. 13
      Apewer/Web/ApiResponse.cs
  35. 92
      Apewer/Web/ApiServiceDescriptor.cs
  36. 23
      Apewer/Web/ApiServiceLifetime.cs
  37. 169
      Apewer/Web/ApiUtility.cs
  38. 65
      Apewer/Web/BytesResult.cs
  39. 44
      Apewer/Web/CacheControl.cs
  40. 12
      Apewer/Web/FromBodyAttribute.cs
  41. 12
      Apewer/Web/FromFormAttribute.cs
  42. 12
      Apewer/Web/FromHeaderAttribute.cs
  43. 12
      Apewer/Web/FromQueryAttribute.cs
  44. 92
      Apewer/Web/HeadResult.cs
  45. 12
      Apewer/Web/HttpConnectAttribute.cs
  46. 12
      Apewer/Web/HttpDeleteAttribute.cs
  47. 12
      Apewer/Web/HttpGetAttribute.cs
  48. 12
      Apewer/Web/HttpHeadAttribute.cs
  49. 10
      Apewer/Web/HttpListenerProvider.cs
  50. 12
      Apewer/Web/HttpOptionsAttribute.cs
  51. 12
      Apewer/Web/HttpPatchAttribute.cs
  52. 12
      Apewer/Web/HttpPostAttribute.cs
  53. 12
      Apewer/Web/HttpPutAttribute.cs
  54. 12
      Apewer/Web/HttpTraceAttribute.cs
  55. 17
      Apewer/Web/IActionResult.cs
  56. 17
      Apewer/Web/IApiModel.cs
  57. 11
      Apewer/Web/IHttpActionResult.cs
  58. 8
      Apewer/Web/MiniConnection.cs
  59. 11
      Apewer/Web/MiniProvider.cs
  60. 9
      Apewer/Web/MiniRequest.cs
  61. 29
      Apewer/Web/RouteAttribute.cs
  62. 24
      Apewer/Web/RoutePrefixAttribute.cs
  63. 120
      Apewer/Web/StreamResult.cs
  64. 20
      Apewer/Web/TextResult.cs
  65. 8
      Apewer/Web/WebsiteProvider.cs
  66. 95
      Apewer/Web/_Attributes.cs
  67. 35
      Apewer/_Extensions.cs
  68. 7
      ChangeLog.md

1
Apewer.Web/AspNetBridge/BridgeController.cs

@ -157,6 +157,7 @@ namespace Apewer.AspNetBridge
} }
// 准备参数。 // 准备参数。
var pis = CollectionUtility.Vacuum(route.Parameters.Map(x => ApiParameter.Parse(x)));
var ps = ReadParameters(Request, route.Parameters); var ps = ReadParameters(Request, route.Parameters);
// 准备控制器。 // 准备控制器。

18
Apewer.Web/Web/AspNetCoreProvider.cs

@ -1,5 +1,6 @@
#if NETCORE #if NETCORE
using Apewer.Network;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -10,13 +11,16 @@ namespace Apewer.Web
{ {
/// <summary>用于网站的服务程序。</summary> /// <summary>用于网站的服务程序。</summary>
public class AspNetCoreProvider : ApiProvider public class AspNetCoreProvider : ApiProvider<HttpContext>
{ {
private HttpContext context; private HttpContext context;
private HttpRequest request; private HttpRequest request;
private HttpResponse response; private HttpResponse response;
/// <summary>HttpContext</summary>
public override HttpContext Context { get => context; }
/// <summary>创建服务程序实例。</summary> /// <summary>创建服务程序实例。</summary>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public AspNetCoreProvider(HttpContext context) public AspNetCoreProvider(HttpContext context)
@ -39,7 +43,7 @@ namespace Apewer.Web
public override Uri GetUrl() public override Uri GetUrl()
{ {
var https = request.IsHttps; var https = request.IsHttps;
var port = context.Connection.LocalPort; var port = Context.Connection.LocalPort;
var query = request.QueryString == null ? null : request.QueryString.Value; var query = request.QueryString == null ? null : request.QueryString.Value;
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -66,11 +70,11 @@ namespace Apewer.Web
public override string GetReferrer() => null; public override string GetReferrer() => null;
/// <summary>获取请求的头。</summary> /// <summary>获取请求的头。</summary>
public override StringPairs GetHeaders() public override HttpHeaders GetHeaders()
{ {
var headers = request.Headers; var headers = request.Headers;
var sp = new StringPairs(); var result = new HttpHeaders();
if (headers == null) return sp; if (headers == null) return result;
foreach (var key in headers.Keys) foreach (var key in headers.Keys)
{ {
if (string.IsNullOrEmpty(key)) continue; if (string.IsNullOrEmpty(key)) continue;
@ -78,11 +82,11 @@ namespace Apewer.Web
{ {
var value = headers[key]; var value = headers[key];
if (string.IsNullOrEmpty(value)) continue; if (string.IsNullOrEmpty(value)) continue;
sp.Add(key, value); result.Add(key, value);
} }
catch { } catch { }
} }
return sp; return result;
} }
/// <summary>获取请求的内容类型。</summary> /// <summary>获取请求的内容类型。</summary>

4
Apewer.Windows/Internals/Interop/Constant.cs

@ -89,6 +89,10 @@ namespace Apewer.Internals.Interop
/// <summary></summary> /// <summary></summary>
public const int PROCESS_QUERY_INFORMATION = 0x400; public const int PROCESS_QUERY_INFORMATION = 0x400;
public const int PROCESS_VM_READ = 0x0010;
public const int PROCESS_VM_WRITE = 0x0020;
/// <summary></summary> /// <summary></summary>
public const int SC_MOVE = 0xF010; public const int SC_MOVE = 0xF010;

16
Apewer.Windows/Internals/Interop/Kernel32.cs

@ -13,7 +13,7 @@ namespace Apewer.Internals.Interop
public static extern int CloseHandle(int hObject); public static extern int CloseHandle(int hObject);
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] // [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject); public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
@ -64,11 +64,12 @@ namespace Apewer.Internals.Interop
[DllImport("kernel32.dll", CharSet = CharSet.Auto)] [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int OpenProcess(int dwDesiredAccess, int bInheritHandle, int dwProcessId); public static extern int OpenProcess(int dwDesiredAccess, int bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)] [DllImport("kernel32.dll", EntryPoint = "OpenProcess")]
public static extern int OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")] // BOOL ReadProcessMemory([in] HANDLE hProcess, [in] LPCVOID lpBaseAddress, [out] LPVOID lpBuffer, [in] SIZE_T nSize, [out] SIZE_T *lpNumberOfBytesRead);
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, IntPtr lpNumberOfBytesRead); [DllImport("kernel32.dll ")]
public static extern bool ReadProcessMemory(IntPtr hProcess, int lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
/// <summary>Copies the contents of a source memory block to a destination memory block, and supports overlapping source and destination memory blocks.</summary> /// <summary>Copies the contents of a source memory block to a destination memory block, and supports overlapping source and destination memory blocks.</summary>
/// <param name="Destination">A pointer to the destination memory block to copy the bytes to.</param> /// <param name="Destination">A pointer to the destination memory block to copy the bytes to.</param>
@ -84,8 +85,9 @@ namespace Apewer.Internals.Interop
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImportAttribute("kernel32.dll")] // BOOL WriteProcessMemory([in] HANDLE hProcess, [in] LPVOID lpBaseAddress, [in] LPCVOID lpBuffer, [in] SIZE_T nSize, [out] SIZE_T *lpNumberOfBytesWritten);
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, int[] lpBuffer, int nSize, IntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);
} }

157
Apewer.Windows/WindowsUtility.cs

@ -25,9 +25,16 @@ namespace Apewer
{ {
/// <summary>Windows 实用工具。</summary> /// <summary>Windows 实用工具。</summary>
public class WindowsUtility public static class WindowsUtility
{ {
#region 句柄
/// <summary>关闭句柄。</summary>
public static bool CloseHandle(IntPtr handle) => Kernel32.CloseHandle(handle);
#endregion
#region 进程。 #region 进程。
#if NETFX #if NETFX
@ -238,45 +245,143 @@ namespace Apewer
return 0; return 0;
} }
/// <summary>读取内存中的值。</summary> /// <summary>打开现有的本地进程对象。</summary>
/// <param name="pid">进程 ID。</param> /// <param name="processId">要打开的本地进程的标识符。</param>
/// <param name="address">地址。</param> /// <returns>指定进程的打开句柄。</returns>
/// <param name="throw">抛出发生的异常。</param> /// <exception cref="ArgumentNullException" />
public static int ReadMemoryInt32(int address, int pid, bool @throw = true) /// <exception cref="SystemException" />
public static IntPtr OpenProcess(int processId)
{
if (processId == 0) throw new ArgumentNullException(nameof(processId));
var handle = Kernel32.OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE, false, processId);
if (handle == IntPtr.Zero) throw new SystemException($"打开进程 {processId} 失败。");
return handle;
}
/// <summary>打开现有的本地进程对象。</summary>
/// <param name="processId">要打开的本地进程的标识符。</param>
/// <param name="callback">使用句柄。</param>
/// <returns>指定进程的打开句柄。</returns>
/// <exception cref="ArgumentNullException" />
/// <exception cref="SystemException" />
public static void OpenProcess(int processId, Action<IntPtr> callback)
{ {
if (processId == 0) throw new ArgumentNullException(nameof(processId));
if (callback == null) throw new ArgumentNullException(nameof(callback));
var handle = IntPtr.Zero;
try try
{ {
var buffer = new byte[4]; handle = OpenProcess(processId);
var pinned = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0); callback.Invoke(handle);
var process = new IntPtr(OpenProcess(0x1F0FFF, false, pid));
ReadProcessMemory(process, (IntPtr)address, pinned, 4, IntPtr.Zero);
CloseHandle(process);
return Marshal.ReadInt32(pinned);
} }
catch (Exception ex) finally
{ {
if (@throw) throw ex; if (handle != IntPtr.Zero) CloseHandle(handle);
return 0;
} }
} }
/// <summary>将值写入指定内存地址中。</summary> /// <summary>打开现有的本地进程对象。</summary>
/// <param name="address">地址。</param> /// <param name="processId">要打开的本地进程的标识符。</param>
/// <param name="pid">进程 ID。</param> /// <param name="callback">使用句柄。</param>
/// <param name="value">Int32 值。</param> /// <returns>指定进程的打开句柄。</returns>
/// <param name="throw">抛出发生的异常。</param> /// <exception cref="ArgumentNullException" />
public static void WriteMemoryInt32(int address, int pid, int value, bool @throw = true) /// <exception cref="SystemException" />
public static T OpenProcess<T>(int processId, Func<IntPtr, T> callback)
{ {
if (processId == 0) throw new ArgumentNullException(nameof(processId));
if (callback == null) throw new ArgumentNullException(nameof(callback));
var handle = IntPtr.Zero;
try try
{ {
var hProcess = OpenProcess(0x1F0FFF, false, pid); // 0x1F0FFF 最高权限 handle = OpenProcess(processId);
WriteProcessMemory(new IntPtr(hProcess), (IntPtr)address, new[] { value }, 4, IntPtr.Zero); return callback.Invoke(handle);
CloseHandle(hProcess);
} }
catch (Exception ex) finally
{
if (handle != IntPtr.Zero) CloseHandle(handle);
}
}
/// <summary>读取指定进程的内存。</summary>
/// <param name="process">进程。</param>
/// <param name="address">要读取的内存地址。</param>
/// <param name="length">要读取的字节数。</param>
/// <returns>读取到的数据。</returns>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="SystemException" />
public static byte[] ReadMemory(this Process process, IntPtr address, int length)
{
if (process == null) throw new ArgumentNullException(nameof(process));
if (address == IntPtr.Zero) throw new ArgumentNullException(nameof(address));
if (length < 1) throw new ArgumentNullException(nameof(length));
return OpenProcess(process.Id, (processHandle) => ReadMemory(processHandle, address, length));
}
/// <summary>读取指定进程的内存。</summary>
/// <param name="processHandle">进程句柄。</param>
/// <param name="address">要读取的内存地址。</param>
/// <param name="length">要读取的字节数。</param>
/// <returns>读取到的数据。</returns>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="SystemException" />
public static byte[] ReadMemory(IntPtr processHandle, IntPtr address, int length)
{
if (processHandle == IntPtr.Zero) throw new ArgumentNullException(nameof(processHandle));
if (address == IntPtr.Zero) throw new ArgumentNullException(nameof(address));
if (length < 1) throw new ArgumentOutOfRangeException(nameof(length)); ;
var buffer = new byte[length];
if (ReadProcessMemory(processHandle, address.ToInt32(), buffer, length, out var read))
{ {
if (@throw) throw ex; if (read == length) return buffer;
// 收缩数组
var array = new byte[read];
if (read > 0) Buffer.BlockCopy(buffer, 0, array, 0, read);
return array;
} }
throw new SystemException($"从内存地址读取数据失败。");
}
/// <summary>在进程的指定地址写入数据。</summary>
/// <param name="process">进程。</param>
/// <param name="address">要读取的内存地址。</param>
/// <param name="data">要写入的数据。</param>
/// <returns>写入的字节数。</returns>
/// <exception cref="ArgumentNullException" />
/// <exception cref="SystemException" />
public static int WriteMemory(this Process process, IntPtr address, byte[] data)
{
if (process == null) throw new ArgumentNullException(nameof(process));
if (address == IntPtr.Zero) throw new ArgumentNullException(nameof(address));
if (data == null) throw new ArgumentNullException(nameof(data));
if (data.Length < 1) return 0;
return OpenProcess(process.Id, processHandle => WriteMemory(processHandle, address, data));
}
/// <summary>在进程的指定地址写入数据。</summary>
/// <param name="processHandle">进程句柄。</param>
/// <param name="address">要读取的内存地址。</param>
/// <param name="data">要写入的数据。</param>
/// <returns>写入的字节数。</returns>
/// <exception cref="ArgumentNullException" />
/// <exception cref="SystemException" />
public static int WriteMemory(IntPtr processHandle, IntPtr address, byte[] data)
{
if (processHandle == IntPtr.Zero) throw new ArgumentNullException(nameof(processHandle));
if (address == IntPtr.Zero) throw new ArgumentNullException(nameof(address));
if (data == null) throw new ArgumentNullException(nameof(data));
if (data.Length < 1) return 0;
if (WriteProcessMemory(processHandle, address, data, data.Length, out var written)) return written;
throw new SystemException($"向内存地址写入数据失败。");
} }
#endregion #endregion

2
Apewer/Apewer.props

@ -9,7 +9,7 @@
<Description></Description> <Description></Description>
<RootNamespace>Apewer</RootNamespace> <RootNamespace>Apewer</RootNamespace>
<Product>Apewer Libraries</Product> <Product>Apewer Libraries</Product>
<Version>6.7.6</Version> <Version>6.8.0</Version>
</PropertyGroup> </PropertyGroup>
<!-- 生成 --> <!-- 生成 -->

81
Apewer/Class.cs

@ -4,73 +4,88 @@ namespace Apewer
{ {
/// <summary>装箱类。</summary> /// <summary>装箱类。</summary>
public sealed class Class<T> : IComparable, IComparable<T>, IComparable<Class<T>> public sealed class Class<T> // : IComparable, IComparable<T>, IComparable<Class<T>>
{ {
private bool _hashcode = false; /// <summary>值。</summary>
private bool _equals = false;
/// <summary>装箱对象。</summary>
public T Value { get; set; } public T Value { get; set; }
/// <summary>创建默认值。</summary> /// <summary>创建装箱实例,值为默认值。</summary>
public Class(T value = default, bool tryEquals = true, bool tryHashCode = true) public Class() { }
{
Value = value; /// <summary>创建装箱实例,值为指定值。</summary>
_hashcode = tryHashCode; public Class(T value) => Value = value;
_equals = tryEquals;
}
#region Override #region Override
/// <summary></summary> /// <summary></summary>
public override int GetHashCode() public override int GetHashCode()
{ {
if (_hashcode && Value != null) if (Value != null) return Value.GetHashCode();
{
return Value.GetHashCode();
}
return base.GetHashCode(); return base.GetHashCode();
} }
/// <summary></summary> /// <summary></summary>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (_equals) if (ReferenceEquals(this, obj)) return true;
var right = obj as Class<T>;
if (Value.IsNull())
{
if (right == null) return true;
if (right.Value.IsNull()) return true;
return false;
}
else
{ {
var right = obj as Class<T>;
if (right == null) return false; if (right == null) return false;
if (right.Value.IsNull()) return false;
if (Value == null && right.Value == null) return true; if (ReferenceEquals(Value, right.Value)) return true;
if (Value == null && right.Value != null) return false;
if (Value != null && right.Value == null) return false;
return Value.Equals(right.Value); return Value.Equals(right.Value);
} }
return base.Equals(obj);
} }
/// <summary></summary> /// <summary></summary>
public override string ToString() public override string ToString()
{ {
if (Value == null) return ""; if (Value == null) return null;
return Value.ToString(); return Value.ToString();
} }
#endregion #endregion
#region IComparable #if ClassCompare
#region Compare
/// <summary></summary> /// <summary></summary>
/// <exception cref="MissingMemberException"></exception>
/// <exception cref="NotSupportedException"></exception> /// <exception cref="NotSupportedException"></exception>
public int CompareTo(object obj) public int CompareTo(object obj)
{ {
if (obj != null && obj is T) return CompareTo((T)obj); if (typeof(IComparable).IsAssignableFrom(typeof(T)))
if (obj != null && obj is Class<T>) return CompareTo(obj as Class<T>); {
var comparable = Value as IComparable;
if (comparable.IsNull())
{
if (obj is Class<T> bro)
{
if (bro == null) return 0;
if (bro.Value.IsNull()) return 0;
return -1;
}
if (obj.IsNull()) return 0;
return -1;
}
else
{
if (obj != null && obj is Class<T> bro) return comparable.CompareTo(bro.Value);
return comparable.CompareTo(obj);
}
}
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value)); throw new NotImplementedException($"类型 {typeof(T).Name} 没有实现 {nameof(IComparable)} 接口。");
if (!(Value is IComparable)) throw new NotSupportedException();
return ((IComparable)Value).CompareTo(obj);
} }
/// <summary></summary> /// <summary></summary>
@ -98,6 +113,8 @@ namespace Apewer
#endregion #endregion
#endif
#region 运算符。 #region 运算符。
/// <summary>从 <see cref="Class{T}"/> 到 Boolean 的隐式转换,判断 <see cref="Class{T}"/> 包含值。</summary> /// <summary>从 <see cref="Class{T}"/> 到 Boolean 的隐式转换,判断 <see cref="Class{T}"/> 包含值。</summary>
@ -112,7 +129,7 @@ namespace Apewer
var text = instance as Class<string>; var text = instance as Class<string>;
if (text != null) return !string.IsNullOrEmpty(text.Value); if (text != null) return !string.IsNullOrEmpty(text.Value);
return instance.NotNull(); return instance != null;
} }
/// <summary>从 <see cref="Class{T}"/> 到 T 的隐式转换。</summary> /// <summary>从 <see cref="Class{T}"/> 到 T 的隐式转换。</summary>

177
Apewer/ClockUtility.cs

@ -1,7 +1,4 @@
using Apewer.Internals; using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
@ -22,11 +19,10 @@ namespace Apewer
if (value is DateTime dt) return dt; if (value is DateTime dt) return dt;
if (value.IsNull()) return null; if (value.IsNull()) return null;
DateTime result;
try try
{ {
var text = value.ToString(); var text = value.ToString();
var parsed = System.DateTime.TryParse(text, out result); var parsed = System.DateTime.TryParse(text, out var result);
return parsed ? new Class<DateTime>(result) : null; return parsed ? new Class<DateTime>(result) : null;
} }
catch catch
@ -46,9 +42,6 @@ namespace Apewer
/// <summary>创建新的零值 DateTime 对象。</summary> /// <summary>创建新的零值 DateTime 对象。</summary>
public static DateTime Zero { get => _zero; } public static DateTime Zero { get => _zero; }
/// <summary>获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000,表示为本地时间。</summary>
public static DateTime Origin { get => _origin; }
/// <summary>获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000,表示为协调通用时间 (UTC)。</summary> /// <summary>获取一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000,表示为协调通用时间 (UTC)。</summary>
public static DateTime UtcOrigin { get => _utc_origin; } public static DateTime UtcOrigin { get => _utc_origin; }
@ -76,6 +69,26 @@ namespace Apewer
#endregion #endregion
#region Round
/// <summary>对齐时间,小于 <see cref="DateTimePart"/> 的部分将被舍弃。</summary>
public static DateTime Round(this DateTime dateTime, DateTimePart datePart)
{
switch (datePart)
{
case DateTimePart.Year: return new DateTime(dateTime.Year, 1, 1, 0, 0, 0, 0, dateTime.Kind);
case DateTimePart.Month: return new DateTime(dateTime.Year, dateTime.Month, 1, 0, 0, 0, 0, dateTime.Kind);
case DateTimePart.Day: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0, 0, dateTime.Kind);
case DateTimePart.Hour: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, 0, 0, 0, dateTime.Kind);
case DateTimePart.Minute: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0, 0, dateTime.Kind);
case DateTimePart.Second: return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, 0, dateTime.Kind);
case DateTimePart.Millisecond: return dateTime;
default: throw new ArgumentException($"指定的 {nameof(DateTimePart)} 不受支持。");
}
}
#endregion
#region Common #region Common
/// <summary>判断指定年份是闰年。</summary> /// <summary>判断指定年份是闰年。</summary>
@ -88,9 +101,10 @@ namespace Apewer
} }
/// <summary>判断指定年份是闰年。</summary> /// <summary>判断指定年份是闰年。</summary>
public static bool IsLeapYear(DateTime datetime) => IsLeapYear(SafeDateTime(datetime).Year); public static bool IsLeapYear(DateTime dateTime) => IsLeapYear(dateTime.Year);
/// <summary>获取指定年月的天数。</summary> /// <summary>获取指定年月的天数。</summary>
/// <exception cref="ArgumentOutOfRangeException" />
public static int MonthDays(int year, int month) public static int MonthDays(int year, int month)
{ {
switch (month) switch (month)
@ -98,41 +112,7 @@ namespace Apewer
case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31;
case 4: case 6: case 9: case 11: return 30; case 4: case 6: case 9: case 11: return 30;
case 2: return IsLeapYear(year) ? 29 : 28; case 2: return IsLeapYear(year) ? 29 : 28;
default: return 0; default: throw new ArgumentOutOfRangeException(nameof(month));
}
}
/// <summary>尝试获取安全的 DateTime 对象。</summary>
public static DateTime SafeDateTime(object datetime)
{
if (datetime is DateTime)
{
var span = (DateTime)datetime - Origin;
return Origin.AddMilliseconds(span.TotalMilliseconds);
}
else
{
if (datetime == null) return Zero;
var type = datetime.GetType();
if (type.Equals(typeof(string)))
{
var s = datetime as string;
if (string.IsNullOrEmpty(s)) return Zero;
DateTime value;
var success = TryParse(s, out value);
return success ? value : Zero;
}
else
{
if (datetime == null) return Zero;
try
{
var s = datetime.ToString();
if (string.IsNullOrEmpty(s)) return Zero;
return SafeDateTime(s);
}
catch { return Zero; }
}
} }
} }
@ -140,35 +120,92 @@ namespace Apewer
#region Stamp #region Stamp
/// <summary>自定义从 DateTime 转为时间戳的方法。</summary>
public static Func<DateTime, long> CustomToStamp { get; set; }
/// <summary>自定义从时间戳转为 DateTime 的方法。</summary>
public static Func<long, DateTime> CustomFromStamp { get; set; }
/// <summary>获取当前本地时间的毫秒时间戳。</summary> /// <summary>获取当前本地时间的毫秒时间戳。</summary>
public static long NowStamp { get => Stamp(Now); } public static long NowStamp { get => ToStamp(Now); }
/// <summary>获取当前 UTC 的毫秒时间戳。</summary> /// <summary>获取当前 UTC 的毫秒时间戳。</summary>
public static long UtcStamp { get => Stamp(UtcNow); } public static long UtcStamp { get => ToStamp(UtcNow); }
/// <summary>获取毫秒时间戳。</summary> /// <summary>获取毫秒时间戳。当指定了 <see cref="CustomToStamp"/> 时将优先使用自定义的方法。</summary>
public static long Stamp(DateTime datetime, bool byMilliseconds = true) /// <remarks>默认不判断参数的时区,与 <see cref="DateTimeKind.Unspecified"/> 相同。</remarks>
public static long ToStamp(DateTime dateTime)
{ {
var span = datetime - Origin; var converter = CustomToStamp;
var value = byMilliseconds ? span.TotalMilliseconds : span.TotalSeconds; if (converter != null) return converter.Invoke(dateTime);
var stamp = Convert.ToInt64(value);
var span = dateTime - _origin;
var value = span.TotalMilliseconds;
var stamp = Convert.ToInt64(Math.Floor(value));
return stamp; return stamp;
} }
/// <summary>从毫秒时间戳获取 DateTime 对象。发生异常且不允许异常时将返回 1970-01-01 00:00:00.000。</summary> /// <summary>获取 UTC 毫秒时间戳</summary>
public static long ToUtcStamp(DateTime dateTime)
{
if (dateTime.Kind == DateTimeKind.Local) dateTime = dateTime.ToUniversalTime();
var span = dateTime - _utc_origin;
var value = span.TotalMilliseconds;
var stamp = Convert.ToInt64(Math.Floor(value));
return stamp;
}
/// <summary>解析毫秒时间戳,获取 DateTime 对象。当指定了 <see cref="CustomFromStamp"/> 时将优先使用自定义的方法。</summary>
/// <remarks>默认不判断系统时区,返回的结果是 <see cref="DateTimeKind.Unspecified"/>。</remarks>
/// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception>
public static DateTime FromStamp(long stamp, DateTimeKind kind = DateTimeKind.Unspecified, bool throwException = true) public static DateTime FromStamp(long stamp)
{ {
try var converter = CustomFromStamp;
{ if (converter != null) return converter.Invoke(stamp);
var origin = NewOrigin(kind); return FromStamp(stamp, DateTimeKind.Unspecified, DateTimeKind.Unspecified);
var datetime = origin.AddMilliseconds(Convert.ToDouble(stamp)); }
return datetime;
} /// <summary>从毫秒时间戳获取 DateTime 对象。</summary>
catch /// <exception cref="ArgumentOutOfRangeException"></exception>
public static DateTime FromStamp(long stamp, DateTimeKind stampKind, DateTimeKind dateTimeKind)
{
switch (dateTimeKind)
{ {
if (throwException) throw new ArgumentOutOfRangeException(); case DateTimeKind.Unspecified:
return Origin; switch (stampKind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
case DateTimeKind.Local:
return _origin.AddMilliseconds(stamp);
default:
throw new ArgumentOutOfRangeException(nameof(stampKind));
}
case DateTimeKind.Utc:
switch (stampKind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
return _utc_origin.AddMilliseconds(stamp);
case DateTimeKind.Local:
return NewOrigin(DateTimeKind.Local).AddMilliseconds(stamp).ToUniversalTime();
default:
throw new ArgumentOutOfRangeException(nameof(stampKind));
}
case DateTimeKind.Local:
switch (stampKind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
return _utc_origin.AddMilliseconds(stamp).ToLocalTime();
case DateTimeKind.Local:
return NewOrigin(DateTimeKind.Local).AddMilliseconds(stamp);
default:
throw new ArgumentOutOfRangeException(nameof(stampKind));
}
default:
throw new ArgumentOutOfRangeException(nameof(dateTimeKind));
} }
} }
@ -229,29 +266,27 @@ namespace Apewer
public static string CompactDate { get { return Compact(Now, true, false, false, false); } } public static string CompactDate { get { return Compact(Now, true, false, false, false); } }
/// <summary>转换 DateTime 对象到易于阅读的文本。</summary> /// <summary>转换 DateTime 对象到易于阅读的文本。</summary>
public static string Lucid(DateTime datetime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) public static string Lucid(DateTime dateTime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true)
{ {
var safe = SafeDateTime(datetime);
var sb = new StringBuilder(); var sb = new StringBuilder();
if (date) sb.Append(FormatDate(safe, true)); if (date) sb.Append(FormatDate(dateTime, true));
if (time) if (time)
{ {
if (date) sb.Append(" "); if (date) sb.Append(" ");
sb.Append(FormatTime(safe, true, seconds, milliseconds)); sb.Append(FormatTime(dateTime, true, seconds, milliseconds));
} }
var lucid = sb.ToString(); var lucid = sb.ToString();
return lucid; return lucid;
} }
/// <summary>转换 DateTime 对象到紧凑的文本。</summary> /// <summary>转换 DateTime 对象到紧凑的文本。</summary>
public static string Compact(DateTime datetime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) public static string Compact(DateTime dateTime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true)
{ {
var safe = SafeDateTime(datetime);
var sb = new StringBuilder(); var sb = new StringBuilder();
if (date) sb.Append(FormatDate(safe, false)); if (date) sb.Append(FormatDate(dateTime, false));
if (time) if (time)
{ {
sb.Append(FormatTime(safe, false, seconds, milliseconds)); sb.Append(FormatTime(dateTime, false, seconds, milliseconds));
} }
var lucid = sb.ToString(); var lucid = sb.ToString();
return lucid; return lucid;

67
Apewer/CollectionUtility.cs

@ -311,7 +311,7 @@ namespace Apewer
/// <summary>对元素去重,且去除 NULL 值。</summary> /// <summary>对元素去重,且去除 NULL 值。</summary>
public static T[] Distinct<T>(IEnumerable<T> items) public static T[] Distinct<T>(IEnumerable<T> items)
{ {
if (items == null) throw new ArgumentNullException(nameof(items)); if (items != null) throw new ArgumentNullException(nameof(items));
var count = Count(items); var count = Count(items);
var added = 0; var added = 0;
var array = new T[count]; var array = new T[count];
@ -346,6 +346,9 @@ namespace Apewer
return array; return array;
} }
/// <summary>清理集合,去除 NULL 值。</summary>
public static T[] Vacuum<T>(this IEnumerable<T> items) => FindAll(items, x => x != null);
/// <summary>获取可枚举集合的部分元素。</summary> /// <summary>获取可枚举集合的部分元素。</summary>
/// <typeparam name="T">集合元素的类型。</typeparam> /// <typeparam name="T">集合元素的类型。</typeparam>
/// <param name="objects">原集合。</param> /// <param name="objects">原集合。</param>
@ -650,10 +653,68 @@ namespace Apewer
#region Find #region Find
/// <summary>根据条件筛选,将符合条件的元素组成新数组。</summary> /// <summary>根据条件筛选,将符合条件的元素组成新数组。</summary>
public static T[] FindAll<T>(this T[] array, Predicate<T> match) => System.Array.FindAll<T>(array, match); public static T[] FindAll<T>(this IEnumerable<T> collection, Predicate<T> match)
{
if (collection == null) new ArgumentNullException(nameof(collection));
if (match == null) throw new ArgumentNullException(nameof(match));
if (collection is T[] array)
{
return System.Array.FindAll<T>(array, match);
}
else if (collection is IList<T> list)
{
var count = list.Count;
var result = new List<T>(count);
for (var i = 0; i < count; i++)
{
var matched = match.Invoke(list[i]);
if (matched) result.Add(list[i]);
}
return result.ToArray();
}
else
{
var result = new List<T>();
foreach (var item in collection)
{
var matched = match.Invoke(item);
if (matched) result.Add(item);
}
return result.ToArray();
}
}
/// <summary>根据条件筛选,找到第一个符合条件的元素。</summary> /// <summary>根据条件筛选,找到第一个符合条件的元素。</summary>
public static T Find<T>(this T[] array, Predicate<T> match) => System.Array.Find<T>(array, match); public static T Find<T>(this IEnumerable<T> collection, Predicate<T> match)
{
if (collection == null) throw new ArgumentNullException(nameof(collection));
if (match == null) throw new ArgumentNullException(nameof(match));
if (collection is T[] array)
{
return System.Array.Find<T>(array, match);
}
else if (collection is IList<T> list)
{
var count = list.Count;
for (var i = 0; i < count; i++)
{
var matched = match.Invoke(list[i]);
if (matched) return list[i];
}
}
else
{
foreach (var item in collection)
{
var matched = match.Invoke(item);
if (matched) return item;
}
}
return default;
}
#endregion #endregion

35
Apewer/DateTimePart.cs

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer
{
/// <summary>表示 DateTime 的部分。</summary>
public enum DateTimePart
{
/// <summary></summary>
Year,
/// <summary></summary>
Month,
/// <summary></summary>
Day,
/// <summary></summary>
Hour,
/// <summary></summary>
Minute,
/// <summary></summary>
Second,
/// <summary></summary>
Millisecond
}
}

224
Apewer/Json.cs

@ -1,6 +1,4 @@
using Apewer; using Newtonsoft.Json;
using Apewer.Internals;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections; using System.Collections;
@ -11,8 +9,6 @@ using System.Dynamic;
using System.IO; using System.IO;
#endif #endif
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using static Apewer.NumberUtility; using static Apewer.NumberUtility;
using static Apewer.RuntimeUtility; using static Apewer.RuntimeUtility;
@ -1739,35 +1735,80 @@ namespace Apewer
if (json._jtoken == null) return null; if (json._jtoken == null) return null;
if (json.TokenType != JTokenType.Object) return null; if (json.TokenType != JTokenType.Object) return null;
var entity = Activator.CreateInstance(typeof(T)); var entity = Object(typeof(T), json, ignoreCase, ignoreCharacters, force);
Object(entity, json, ignoreCase, ignoreCharacters, force); return entity == null ? default : (T)entity;
return (T)entity;
} }
/// <summary>将 Json 填充到数组列表,失败时返回 NULL 值。</summary> /// <summary>将 Json 填充到数组列表,失败时返回 NULL 值。</summary>
internal static T[] Array<T>(Json json, bool ignoreCase = true, string ignoreCharacters = null, bool force = false) internal static TItem[] Array<TItem>(Json json, bool ignoreCase = true, string ignoreCharacters = null, bool force = false)
{ {
if (json == null) return null; if (json == null) return null;
if (json._jtoken == null) return null; if (json._jtoken == null) return null;
if (json.TokenType != JTokenType.Array) return null; if (json.TokenType != JTokenType.Array) return null;
var list = new List<T>(); var instance = Array(typeof(TItem), json, ignoreCase, ignoreCharacters, force);
Array(list, json, ignoreCase, ignoreCharacters, force); var array = (TItem[])instance;
return list.ToArray(); return array;
} }
/// <summary></summary> static ConstructorInfo GetDeserializeConstructor(Type type)
public static void Object(object entity, Json json, bool ignoreCase, string ignoreCharacters, bool force) {
var types = new Type[] { typeof(Json) };
var independent = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, types, null);
if (independent == null) independent = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
return independent;
}
/// <summary>创建指定类型的新实例,将 Json 的内容填充到新实例中。</summary>
public static object Object(Type type, Json json, bool ignoreCase, string ignoreCharacters, bool force)
{ {
if (entity == null || json == null) return; // 检查参数。
if (json.TokenType != JTokenType.Object) return; if (type == null) throw new ArgumentNullException(nameof(type));
if (json == null || json._jtoken == null) return null;
// 类型自己实现反序列化。
var independent = GetDeserializeConstructor(type);
if (independent != null) return independent.Invoke(new object[] { json });
// 是数组。
if (type.IsArray)
{
var itemType = GetTypeOfArrayItem(type);
return Array(itemType, json, ignoreCase, ignoreCharacters, force);
}
// 是列表
if (typeof(IList).IsAssignableFrom(type))
{
var genericTypes = type.GetGenericArguments();
if (genericTypes != null && genericTypes.Length == 1)
{
var genericType = genericTypes[0];
if (genericType != null)
{
var array = Array(genericType, json, ignoreCase, ignoreCharacters, force);
var list = ArrayToList(array, type);
return list;
}
}
}
// 必须是有效的 Json 实例。
if (json.TokenType != JTokenType.Object && json.TokenType != JTokenType.Array) return null;
// 必须有无参数的构造函数。
var constructor = type.GetConstructor(System.Type.EmptyTypes);
if (constructor == null) return null;
// 创建实例。
var entity = constructor.Invoke(null);
var jps = json.GetProperties(); var jps = json.GetProperties();
if (jps.Length < 1) return; if (jps.Length < 1) return entity;
var etype = entity.GetType(); var etype = entity.GetType();
var eps = etype.GetProperties(); var eps = etype.GetProperties();
if (eps.Length < 1) return; if (eps.Length < 1) return entity;
foreach (var ep in eps) foreach (var ep in eps)
{ {
@ -1801,6 +1842,8 @@ namespace Apewer
Property(entity, ep, value, ignoreCase, ignoreCharacters, force); Property(entity, ep, value, ignoreCase, ignoreCharacters, force);
} }
} }
return entity;
} }
static void Add(object entity, object item, int index) static void Add(object entity, object item, int index)
@ -1816,64 +1859,54 @@ namespace Apewer
} }
} }
internal static void Array(object array, Json json, bool ignoreCase, string ignoreCharacters, bool force) /// <exception cref="ArgumentNullException" />
internal static object Array(Type itemType, Json json, bool ignoreCase, string ignoreCharacters, bool force)
{ {
if (array == null) return; if (itemType == null) throw new ArgumentNullException(nameof(itemType));
if (json == null) return; if (json == null) return null;
if (json.TokenType != JTokenType.Array) return;
var type = array.GetType(); // 必须是有效的 Json 实例。
var subtype = null as Type; if (json.TokenType != JTokenType.Array) return null;
if (array is Array)
{ // 加入列表。
var arrayType = array.GetType(); var items = json.GetItems();
subtype = RuntimeUtility.GetTypeOfArrayItem(arrayType); var list = new List<object>(items.Length);
} for (var index = 0; index < items.Length; index++)
else {
{ var item = items[index];
var subtypes = type.GetGenericArguments(); if (item == null) list.Add(null);
if (subtypes.Length < 1) return; else if (itemType.Equals(typeof(Json))) list.Add(item);
subtype = subtypes[0]; else if (itemType.Equals(typeof(string))) list.Add(item ? item.Text : null);
} else if (itemType.Equals(typeof(byte))) list.Add(Byte(item.Text));
else if (itemType.Equals(typeof(short))) list.Add(Int16(item.Text));
var jis = json.GetItems(); else if (itemType.Equals(typeof(int))) list.Add(Int32(item.Text));
for (var index = 0; index < jis.Length; index++) else if (itemType.Equals(typeof(long))) list.Add(Int64(item.Text));
{ else if (itemType.Equals(typeof(sbyte))) list.Add(SByte(item.Text));
var ji = jis[index]; else if (itemType.Equals(typeof(ushort))) list.Add(UInt16(item.Text));
if (subtype.Equals(typeof(Json))) Add(array, ji, index); else if (itemType.Equals(typeof(uint))) list.Add(UInt32(item.Text));
else if (subtype.Equals(typeof(string))) Add(array, (ji.TokenType == JTokenType.Null) ? null : ji.Text, index); else if (itemType.Equals(typeof(ulong))) list.Add(UInt64(item.Text));
else if (subtype.Equals(typeof(byte))) Add(array, Byte(ji.Text), index); else if (itemType.Equals(typeof(float))) list.Add(Single(item.Text));
else if (subtype.Equals(typeof(short))) Add(array, Int16(ji.Text), index); else if (itemType.Equals(typeof(double))) list.Add(Double(item.Text));
else if (subtype.Equals(typeof(int))) Add(array, Int32(ji.Text), index); else if (itemType.Equals(typeof(decimal))) list.Add(Decimal(item.Text));
else if (subtype.Equals(typeof(long))) Add(array, Int64(ji.Text), index);
else if (subtype.Equals(typeof(sbyte))) Add(array, SByte(ji.Text), index);
else if (subtype.Equals(typeof(ushort))) Add(array, UInt16(ji.Text), index);
else if (subtype.Equals(typeof(uint))) Add(array, UInt32(ji.Text), index);
else if (subtype.Equals(typeof(ulong))) Add(array, UInt64(ji.Text), index);
else if (subtype.Equals(typeof(float))) Add(array, Single(ji.Text), index);
else if (subtype.Equals(typeof(double))) Add(array, Double(ji.Text), index);
else if (subtype.Equals(typeof(decimal))) Add(array, Decimal(ji.Text), index);
else else
{ {
var serializable = (force || _forceall) ? true : CanSerialize(subtype, false); var serializable = (force || _forceall) ? true : CanSerialize(itemType, false);
if (serializable && (ji is Json)) if (serializable)
{ {
switch (ji.TokenType) var itemInstance = Object(itemType, item, ignoreCase, ignoreCharacters, force);
{ list.Add(itemInstance);
case JTokenType.Object: }
var subobject = Activator.CreateInstance(subtype); else
Object(subobject, ji, ignoreCase, ignoreCharacters, force); {
Add(array, subobject, index); list.Add(null);
break;
case JTokenType.Array:
var subarray = Activator.CreateInstance(subtype);
Array(subarray, ji, ignoreCase, ignoreCharacters, force);
Add(array, subarray, index);
break;
}
} }
} }
} }
// 输出数组。
var array = System.Array.CreateInstance(itemType, list.Count);
for (var i = 0; i < list.Count; i++) array.SetValue(list[i], i);
return array;
} }
private static void Property(object entity, PropertyInfo property, object value, bool ignoreCase, string ignoreCharacters, bool force) private static void Property(object entity, PropertyInfo property, object value, bool ignoreCase, string ignoreCharacters, bool force)
@ -1930,36 +1963,51 @@ namespace Apewer
{ {
var serializable = (force || _forceall); var serializable = (force || _forceall);
if (!serializable) serializable = CanSerialize(property.PropertyType, false); if (!serializable) serializable = CanSerialize(property.PropertyType, false);
if (serializable && (value is Json)) if (serializable && value is Json json)
{ {
switch (((Json)value).TokenType) if (pt.IsArray)
{ {
case JTokenType.Object: var array = Array(pt.GetElementType(), json, ignoreCase, ignoreCharacters, force);
var subobject = Activator.CreateInstance(property.PropertyType); setter.Invoke(entity, array);
Object(subobject, (Json)value, ignoreCase, ignoreCharacters, force); }
setter.Invoke(entity, new object[] { subobject }); else if (typeof(IList).IsAssignableFrom(pt))
break; {
case JTokenType.Array: var genericTypes = pt.GetGenericArguments();
object subarray; if (genericTypes != null && genericTypes.Length == 1)
if (pt.BaseType != null && pt.BaseType.Equals(typeof(Array))) {
{ var genericType = genericTypes[0];
subarray = new object(); if (genericType != null)
var length = ((Json)value).GetItems().Length;
subarray = pt.InvokeMember("Set", BindingFlags.CreateInstance, null, subarray, new object[] { length });
}
else
{ {
subarray = Activator.CreateInstance(property.PropertyType); var array = Array(genericType, json, ignoreCase, ignoreCharacters, force);
var list = ArrayToList(array, pt);
setter.Invoke(entity, list);
} }
Array(subarray, (Json)value, ignoreCase, ignoreCharacters, force); }
setter.Invoke(entity, new object[] { subarray }); }
break; else
{
var @object = Object(pt, json, ignoreCase, ignoreCharacters, force);
setter.Invoke(entity, @object);
} }
} }
} }
} }
} }
static IList ArrayToList(object array, Type listType)
{
if (listType == null) throw new ArgumentNullException(nameof(listType));
if (array != null && array is Array a)
{
var list = Activator.CreateInstance(listType) as IList;
for (var i = 0; i < a.Length; i++) list.Add(a.GetValue(i));
return list;
}
return null;
}
#endregion #endregion
#endregion #endregion

52
Apewer/Network/HttpBody.cs

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Apewer.Network
{
/// <summary>HTTP 正文。</summary>
public abstract class HttpBody { }
/// <summary>HTTP 报文结构。</summary>
public sealed class HttpBytesMessage : HttpBody
{
/// <summary>主体。</summary>
public byte[] Bytes { get; set; }
}
/// <summary>HTTP 报文结构。</summary>
public class HttpStreamMessage<T> : HttpBody where T : Stream
{
/// <summary>自动释放 Stream 对象。</summary>
public bool AutoDispose { get; set; }
/// <summary>主体。</summary>
public Stream Stream { get; set; }
/// <summary>主体的长度。</summary>
public long Length { get; set; }
}
/// <summary>HTTP 报文结构。</summary>
public sealed class HttpStreamMessage : HttpBody
{
/// <summary>自动释放 Stream 对象。</summary>
public bool AutoDispose { get; set; }
/// <summary>主体。</summary>
public Stream Stream { get; set; }
/// <summary>主体的长度。</summary>
public long Length { get; set; }
}
}

47
Apewer/Network/HttpHeader.cs

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Network
{
/// <summary>HTTP 头。</summary>
[Serializable]
public sealed class HttpHeader
{
string _name = null;
string _value = null;
/// <summary>名称。</summary>
public string Name { get => _name; set => _name = value?.Trim(); }
/// <summary>值。</summary>
public string Value { get => _value; set => _value = value?.Trim(); }
/// <summary>创建 HTTP 头的实例。</summary>
public HttpHeader() { }
/// <summary>创建 HTTP 头的实例。</summary>
/// <exception cref="ArgumentException" />
public HttpHeader(KeyValuePair<string, string> keyValuePair)
{
if (keyValuePair.Key.IsEmpty()) throw new ArgumentNullException("Key 无效。");
Name = keyValuePair.Key;
Value = keyValuePair.Value;
}
/// <summary>创建 HTTP 头的实例。</summary>
/// <exception cref="ArgumentNullException" />
public HttpHeader(string name, string value)
{
if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
Name = name;
Value = value;
}
}
}

369
Apewer/Network/HttpHeaders.cs

@ -0,0 +1,369 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace Apewer.Network
{
/// <summary>HTTP 头的集合。</summary>
[Serializable]
public sealed class HttpHeaders : IEnumerable<HttpHeader>, ICollection<HttpHeader>, IToJson
{
#region IEnumerable
List<HttpHeader> _list = new List<HttpHeader>();
long _version = 0L;
/// <summary>获取枚举器。</summary>
public IEnumerator<HttpHeader> GetEnumerator() => new Enumerator(this);
/// <summary>获取枚举器。</summary>
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
/// <summary></summary>
public sealed class Enumerator : IEnumerator<HttpHeader>
{
HttpHeaders _headeres = null;
long _version = 0L;
bool _disposed = false;
HttpHeader _current = null;
int _index = 0;
const string ObjectName = nameof(Enumerator);
const string OriginChanged = "原集合已变更,无法继续遍历。";
/// <summary></summary>
public HttpHeader Current
{
get
{
if (_disposed) throw new ObjectDisposedException(ObjectName);
if (_version != _headeres._version) throw new InvalidOperationException(OriginChanged);
return _current;
}
}
/// <summary></summary>
object IEnumerator.Current { get => Current; }
/// <summary></summary>
public void Dispose() { _disposed = true; }
/// <summary></summary>
public bool MoveNext()
{
if (_disposed) throw new ObjectDisposedException(ObjectName);
if (_version != _headeres._version) throw new InvalidOperationException(OriginChanged);
if (_index < _headeres._list.Count)
{
_current = _headeres._list[_index];
_index++;
return true;
}
return false;
}
/// <summary></summary>
public void Reset()
{
_current = null;
_index = 0;
}
/// <exception cref="ArgumentNullException" />
public Enumerator(HttpHeaders headers)
{
if (headers == null) throw new ArgumentNullException(nameof(headers));
_headeres = headers;
_version = headers._version;
Reset();
}
}
#endregion
#region ICollection
/// <summary>元素数量。</summary>
public int Count { get => _list.Count; }
/// <summary>当前集合是只读。</summary>
public bool IsReadOnly { get => false; }
/// <summary>添加一项。</summary>
/// <exception cref="ArgumentNullException" />
public HttpHeader Add(string name, string value)
{
if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
var header = new HttpHeader(name, value);
_list.Add(header);
_version++;
return header;
}
/// <summary>添加一项。</summary>
/// <exception cref="ArgumentNullException" />
public void Add(HttpHeader header)
{
if (header == null) throw new ArgumentNullException(nameof(header));
_list.Add(header);
_version++;
}
/// <summary>移除所有元素。</summary>
public void Clear() => _list.Clear();
/// <summary>判断是否包含指定的元素。</summary>
public bool Contains(HttpHeader item) => _list.Contains(item);
/// <summary>复制所有元素到数组。</summary>
/// <param name="array">目标数组。</param>
/// <param name="arrayIndex">数组的位置。</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="ArgumentException" />
public void CopyTo(HttpHeader[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
/// <summary>移除指定的元素</summary>
public bool Remove(HttpHeader item) => _list.Remove(item);
#endregion
#region IList
/// <summary></summary>
/// <exception cref="ArgumentOutOfRangeException" />
public HttpHeader this[int index] { get => _list[index]; set => _list[index] = value; }
/// <summary></summary>
/// <exception cref="ArgumentOutOfRangeException" />
public string this[string name] { get => GetValue(name); set => SetValue(name, value); }
/// <summary>搜索指定对象在当前集合中的位置索引。</summary>
public int IndexOf(HttpHeader item) => _list.IndexOf(item);
/// <summary>插入元素到指定位置。</summary>
/// <exception cref="ArgumentOutOfRangeException" />
public void Insert(int index, HttpHeader item) => _list.Insert(index, item);
/// <summary>移除指定位置的元素。</summary>
/// <exception cref="ArgumentOutOfRangeException" />
public void RemoveAt(int index) => _list.RemoveAt(index);
#endregion
#region constructor
/// <summary></summary>
public HttpHeaders() { }
/// <summary></summary>
/// <exception cref="ArgumentNullException" />
public HttpHeaders(params HttpHeader[] headers) : this(headers as IEnumerable<HttpHeader>) { }
/// <summary></summary>
/// <exception cref="ArgumentNullException" />
public HttpHeaders(IEnumerable<HttpHeader> headers)
{
if (headers == null) throw new ArgumentNullException(nameof(headers));
foreach (var header in headers)
{
if (header == null) continue;
if (header.Name.IsEmpty()) continue;
Add(header);
}
}
/// <summary></summary>
/// <exception cref="ArgumentNullException" />
public HttpHeaders(IEnumerable<KeyValuePair<string, string>> headers)
{
if (headers == null) return;
foreach (var header in headers)
{
if (header.Key.IsEmpty()) continue;
Add(header.Key, header.Value);
}
}
/// <summary></summary>
/// <exception cref="ArgumentNullException" />
public HttpHeaders(NameValueCollection headers)
{
if (headers == null) return;
var keys = headers.AllKeys;
foreach (var key in keys)
{
if (key.IsEmpty()) continue;
var value = headers[key];
Add(key, value);
}
}
#endregion
#region operation
/// <summary>获取匹配 Name 的 Value。不存在 Name 时返回 NULL 值。</summary>
/// <exception cref="ArgumentNullException" />
public string GetValue(string name)
{
if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
// 精准匹配。
var count = _list.Count;
for (var i = 0; i < count; i++)
{
var item = _list[i];
if (string.IsNullOrEmpty(item.Name)) continue;
if (item.Name == name)
{
if (string.IsNullOrEmpty(item.Value)) continue;
return item.Value;
}
}
// 忽略大小写。
var lower = TextUtility.Lower(name);
for (var i = 0; i < count; i++)
{
var item = _list[i];
if (string.IsNullOrEmpty(item.Name)) continue;
if (TextUtility.Lower(item.Name) == lower)
{
if (string.IsNullOrEmpty(item.Value)) continue;
return item.Value;
}
}
return null;
}
/// <summary>获取匹配 Name 的 Value。不存在 Name 时返回 NULL 值。</summary>
/// <exception cref="ArgumentNullException" />
public string[] GetValues(string name)
{
if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
// 忽略大小写。
// name = TextUtility.Lower(name);
var count = _list.Count;
var values = new List<string>(_list.Count);
for (var i = 0; i < count; i++)
{
var item = _list[i];
if (string.IsNullOrEmpty(item.Name)) continue;
if (string.Equals(item.Name, name, StringComparison.CurrentCultureIgnoreCase))
{
var value = item.Value.ToTrim();
if (string.IsNullOrEmpty(value)) continue;
values.Add(value);
}
}
return values.ToArray();
}
/// <summary>设置 Name 的 Value。不存在 Name 时添加新元素。</summary>
/// <exception cref="ArgumentNullException" />
public HttpHeader SetValue(string name, string value)
{
if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
// 尝试精准匹配。
var count = _list.Count;
for (var i = 0; i < count; i++)
{
var item = _list[i];
if (string.IsNullOrEmpty(item.Name)) continue;
if (item.Name == name)
{
item.Value = value;
_version++;
return item;
}
}
// 尝试模糊匹配。
var lower = TextUtility.Lower(name);
for (var i = 0; i < count; i++)
{
var item = _list[i];
if (TextUtility.Lower(item.Name) == lower)
{
item.Value = value;
_version++;
return item;
}
}
// 添加新项。
return Add(name, value);
}
/// <summary>每个元素组成为新数组。</summary>
public HttpHeader[] ToArray() => _list.ToArray();
#endregion
#region Json
internal HttpHeaders(Json json)
{
if (!json) return;
if (json.IsObject)
{
var properties = json.GetProperties();
foreach (var property in properties)
{
if (property == null) continue;
if (property.Name.IsEmpty()) continue;
Add(property.Name, property.Value?.ToString());
}
}
if (json.IsArray)
{
var array = json.Array<HttpHeader>();
if (array != null)
{
for (var i = 0; i < array.Length; i++)
{
if (array[i] == null) continue;
if (array[i].Name.IsEmpty()) continue;
_list.Add(array[i]);
}
}
}
}
/// <summary></summary>
public Json ToJson()
{
var array = Json.NewArray();
var count = 0;
for (var i = 0; i < count; i++)
{
var item = Json.From(_list[i]);
array.AddItem(item);
}
return array;
}
#endregion
}
}

18
Apewer/Network/HttpMethod.cs

@ -11,31 +11,31 @@ namespace Apewer.Network
/// <summary></summary> /// <summary></summary>
NULL, NULL,
/// <summary>The CONNECT method establishes a tunnel to the server identified by the target resource.</summary> /// <summary>CONNECT 方法建立一个到由目标资源标识的服务器的隧道。</summary>
CONNECT, CONNECT,
/// <summary>The DELETE method deletes the specified resource.</summary> /// <summary>DELETE 方法删除指定的资源。</summary>
DELETE, DELETE,
/// <summary>The GET method requests a representation of the specified resource. Requests using GET should only retrieve data.</summary> /// <summary>GET 方法请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。</summary>
GET, GET,
/// <summary>The HEAD method asks for a response identical to that of a GET request, but without the response body.</summary> /// <summary>HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。</summary>
HEAD, HEAD,
/// <summary>The OPTIONS method is used to describe the communication options for the target resource.</summary> /// <summary>OPTIONS 方法用于描述目标资源的通信选项。</summary>
OPTIONS, OPTIONS,
/// <summary>The PATCH method is used to apply partial modifications to a resource.</summary> /// <summary>PATCH 方法用于对资源应用部分修改。</summary>
PATCH, PATCH,
/// <summary>The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server.</summary> /// <summary>POST 方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。</summary>
POST, POST,
/// <summary>The PUT method replaces all current representations of the target resource with the request payload.</summary> /// <summary>PUT 方法用有效载荷请求替换目标资源的所有当前表示。</summary>
PUT, PUT,
/// <summary>The TRACE method performs a message loop-back test along the path to the target resource.</summary> /// <summary>TRACE 方法沿着到目标资源的路径执行一个消息环回测试。</summary>
TRACE TRACE
} }

28
Apewer/RuntimeUtility.cs

@ -229,32 +229,14 @@ namespace Apewer
/// <exception cref="TargetException"></exception> /// <exception cref="TargetException"></exception>
/// <exception cref="TargetInvocationException"></exception> /// <exception cref="TargetInvocationException"></exception>
/// <exception cref="TargetParameterCountException"></exception> /// <exception cref="TargetParameterCountException"></exception>
public static object InvokeMethod(object instance, MethodInfo method, object parameter) public static object Invoke(this MethodInfo method, object instance, params object[] parameters)
{ {
if (instance == null || method == null) return null; if (instance == null || method == null) return null;
{
var pis = method.GetParameters();
if (pis == null || pis.Length < 1) return method.Invoke(instance, null);
}
return method.Invoke(instance, new object[] { parameter });
}
/// <summary>调用方法。</summary> var pis = method.GetParameters();
/// <exception cref="ArgumentException"></exception> if (pis == null || pis.Length < 1) return method.Invoke(instance, null);
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="MethodAccessException"></exception> if (parameters == null) return method.Invoke(instance, null);
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="TargetException"></exception>
/// <exception cref="TargetInvocationException"></exception>
/// <exception cref="TargetParameterCountException"></exception>
public static object InvokeMethod(object instance, MethodInfo method, params object[] parameters)
{
if (instance == null || method == null) return null;
{
var pis = method.GetParameters();
if (pis == null || pis.Length < 1) return method.Invoke(instance, null);
}
if (parameters == null) return method.Invoke(instance, new object[] { null });
return method.Invoke(instance, parameters); return method.Invoke(instance, parameters);
} }

24
Apewer/Web/ActionResult.cs

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>API 结果。</summary>
public abstract class ActionResult : IActionResult, IHttpActionResult, IDisposable
{
/// <summary>释放系统资源。</summary>
public virtual void Dispose() { }
// /// <summary>释放系统资源。</summary>
// /// <param name="disposing">TRUE:释放托管资源和非托管资源。FALSE:释放非托管资源。</param>
// protected virtual void Dispose(bool disposing) { }
/// <summary>执行结果。</summary>
public abstract void ExecuteResult(ApiContext context);
}
}

240
Apewer/Web/ApiAction.cs

@ -0,0 +1,240 @@
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
}
}

33
Apewer/Web/ApiActionJsonFormat.cs

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>类型 <see cref="ApiAction"/> 的实例转为 <see cref="Json"/> 时的格式。</summary>
[Serializable]
public sealed class ApiActionJsonFormat
{
/// <summary>默认格式。</summary>
public static ApiActionJsonFormat Default { get; set; }
/// <summary>包含参数。</summary>
/// <value>TRUE(默认值)</value>
public bool WithParameters { get; set; }
/// <summary>包含反射信息。</summary>
/// <value>TRUE(默认值)</value>
public bool WithReflection { get; set; }
/// <summary>创建 <see cref="ApiActionJsonFormat"/> 的实例。</summary>
public ApiActionJsonFormat()
{
WithParameters = true;
WithReflection = true;
}
}
}

177
Apewer/Web/ApiApplication.cs

@ -5,47 +5,176 @@ using System.Reflection;
namespace Apewer.Web namespace Apewer.Web
{ {
internal sealed class ApiApplication /// <summary></summary>
public sealed class ApiApplication : IToJson
{ {
internal Dictionary<string, ApiFunction> Functions = null; #region fields
internal List<ApiFunction> Items = null;
internal Type Type; Type _type = null;
internal string Module; string _module = null;
// 主特性和主要属性。 // ApiAttribute
internal ApiAttribute Attribute; string _name = null;
internal string Name; string _lower = null;
internal string Lower; string _caption = null;
internal string Caption; string _description = null;
internal string Description;
// 附加特性。 // invoke & enumerate
internal bool Independent; bool _independent = false;
internal bool Hidden; bool _hidden = false;
internal ApiFunction Get(string name) // functions
Dictionary<string, ApiFunction> _dict = new Dictionary<string, ApiFunction>();
List<ApiFunction> _list = new List<ApiFunction>();
#endregion
#region properties
/// <summary></summary>
public Type Type { get => _type; }
/// <summary></summary>
public string Module { get => _module; }
/// <summary></summary>
public string Name { get => _name; }
/// <summary></summary>
public string Caption { get => _caption; }
/// <summary></summary>
public string Description { get => _description; }
/// <summary></summary>
public bool Independent { get => _independent; }
/// <summary></summary>
public bool Hidden { get => _hidden; }
/// <summary></summary>
public ApiFunction[] Functions { get => _list.ToArray(); }
#endregion
/// <summary></summary>
public ApiApplication(Type type, ApiAttribute api)
{
// type
_type = type;
// api
if (api == null)
{
_name = type?.Name;
}
else
{
_name = string.IsNullOrEmpty(api.Name) ? type?.Name : api.Name;
_caption = api.Caption;
_description = api.Description;
}
if (type != null)
{
// caption
if (string.IsNullOrEmpty(_caption))
{
var captions = type.GetCustomAttributes(typeof(CaptionAttribute), true);
if (captions.Length > 0)
{
var caption = (CaptionAttribute)captions[0];
_caption = caption.Title;
if (string.IsNullOrEmpty(_description))
{
_description = caption.Description;
}
}
}
// hidden
if (type.Contains<HiddenAttribute>(false)) _hidden = true;
// independent
if (type.Contains<IndependentAttribute>(false)) _independent = true;
// Module
var assemblyName = type.Assembly.GetName();
_module = TextUtility.Join("-", assemblyName.Name, assemblyName.Version.ToString());
// functions
var funcs = new Dictionary<string, ApiFunction>();
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (var method in methods)
{
var func = ApiFunction.Parse(this, method);
if (func == null) continue;
var funcKey = func.Name.Lower();
if (funcs.ContainsKey(funcKey)) continue;
funcs.Add(funcKey, func);
}
_dict = funcs;
_list.AddRange(funcs.Values);
_list.Sort(new Comparison<ApiFunction>((a, b) => a.Name.CompareTo(b.Name)));
}
}
internal ApiFunction GetFunction(string name)
{ {
if (string.IsNullOrEmpty(name)) return null; if (string.IsNullOrEmpty(name)) return null;
var lower = name.ToLower(); if (_dict.TryGetValue(name.ToLower(), out var func)) return func;
ApiFunction func; return null;
var exist = Functions.TryGetValue(lower, out func);
return func;
} }
/// <summary></summary>
public Json ToJson() => ToJson(new ApiOptions());
internal Json ToJson(ApiOptions options) internal Json ToJson(ApiOptions options)
{ {
if (Hidden) return null; if (Hidden) return null;
var json = Json.NewObject(); var json = Json.NewObject();
json.SetProperty("name", Name); json.SetProperty("name", _name);
if (!string.IsNullOrEmpty(Caption)) json.SetProperty("caption", Caption); if (!string.IsNullOrEmpty(_caption)) json.SetProperty("caption", _caption);
if (!string.IsNullOrEmpty(Description)) json.SetProperty("description", Description); if (!string.IsNullOrEmpty(_description)) json.SetProperty("description", _description);
if (options.WithTypeName) json.SetProperty("type", Type.FullName); if (options != null)
if (options.WithModuleName) json.SetProperty("mudule", Module); {
if (options.WithTypeName) json.SetProperty("type", _type.FullName);
if (options.WithModuleName) json.SetProperty("mudule", _module);
if (options.AllowEnumerate) json.SetProperty("functions", Json.From(_list));
}
return json; return json;
} }
/// <summary>解析类型,获取 <see cref="ApiApplication"/> 实例。</summary>
/// <param name="type">要解析的类型。</param>
/// <param name="requireAttribute">要求此类型拥有 Api 特性。</param>
/// <returns>解析成功返回实例,解析失败返回 NULL 值。</returns>
public static ApiApplication Parse(Type type, bool requireAttribute)
{
if (type == null) return null;
// 检查类型的属性。
if (!type.IsClass) return null;
if (type.IsAbstract) return null;
if (type.IsGenericType) return null;
if (type.GetGenericArguments().NotEmpty()) return null;
if (!RuntimeUtility.CanNew(type)) return null;
// 判断基类。
if (!typeof(ApiController).IsAssignableFrom(type)) return null;
// 检查类型的特性。
var apis = type.GetCustomAttributes(typeof(ApiAttribute), false);
var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
if (requireAttribute && api == null) return null;
return new ApiApplication(type, api);
}
} }
} }

15
Apewer/Web/ApiCatch.cs

@ -9,23 +9,18 @@ namespace Apewer.Web
public sealed class ApiCatch public sealed class ApiCatch
{ {
ApiController _controller = null; ApiContext _context = null;
ApiOptions _options = null;
Exception _exception = null; Exception _exception = null;
/// <summary>调度程序调用的控制器。</summary> /// <summary>上下文。</summary>
public ApiController Controller { get => _controller; } public ApiContext Context { get => _context; }
/// <summary>调度程序使用的 API 选项。</summary>
public ApiOptions Options { get => _options; }
/// <summary>已捕获的异常。</summary> /// <summary>已捕获的异常。</summary>
public Exception Exception { get => _exception; } public Exception Exception { get => _exception; }
internal ApiCatch(ApiController controller, ApiOptions options, Exception exception) internal ApiCatch(ApiContext context, Exception exception)
{ {
_controller = controller; _context = context;
_options = options;
_exception = exception; _exception = exception;
} }

8
Apewer/Web/ApiContext.cs

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection;
using System.Text; using System.Text;
namespace Apewer.Web namespace Apewer.Web
@ -39,8 +40,8 @@ namespace Apewer.Web
#region 执行过程中产生的内容 #region 执行过程中产生的内容
/// <summary>API 入口。</summary> /// <summary>API 行为。</summary>
public ApiEntry Entry { get; internal set; } public ApiAction ApiAction { get; internal set; }
/// <summary>API 请求。</summary> /// <summary>API 请求。</summary>
public ApiRequest Request { get; internal set; } public ApiRequest Request { get; internal set; }
@ -51,6 +52,9 @@ namespace Apewer.Web
/// <summary>API 控制器实例。</summary> /// <summary>API 控制器实例。</summary>
public ApiController Controller { get; internal set; } public ApiController Controller { get; internal set; }
/// <summary>执行的方法。</summary>
public MethodInfo MethodInfo { get; internal set; }
#endregion #endregion
internal ApiContext(ApiInvoker invoker, ApiProvider provider, ApiEntries entries) internal ApiContext(ApiInvoker invoker, ApiProvider provider, ApiEntries entries)

333
Apewer/Web/ApiEntries.cs

@ -7,272 +7,181 @@ namespace Apewer.Web
{ {
/// <summary>入口集合。</summary> /// <summary>入口集合。</summary>
public sealed class ApiEntries public sealed class ApiEntries : IToJson
{ {
#region 实例。 #region instance
object locker = new object(); object locker = new object();
Dictionary<string, ApiApplication> Applications = null; SortedDictionary<string, ApiApplication> _apps = new SortedDictionary<string, ApiApplication>();
List<ApiApplication> Items = null; SortedDictionary<string, ApiAction> _actions = new SortedDictionary<string, ApiAction>();
internal ApiApplication Get(string name) internal ApiApplication GetApplication(string name)
{ {
if (string.IsNullOrEmpty(name)) return null; if (string.IsNullOrEmpty(name)) return null;
if (Applications == null) return null;
var lower = name.ToLower();
ApiApplication app;
var found = false;
lock (locker) { found = Applications.TryGetValue(lower, out app); }
return found ? app : null;
}
internal List<ApiApplication> Enumerate() => Items;
/// <summary>清空当前实例。</summary> var key = name.ToLower();
public void Clear()
{
lock (locker) lock (locker)
{ {
Applications = new Dictionary<string, ApiApplication>(); if (_apps.TryGetValue(key, out var value)) return value;
Items = new List<ApiApplication>();
} }
return null;
} }
/// <summary>追加指定的集合,指定 replace 参数将替换当前实例中的同名的入口。</summary> internal ApiAction GetAction(string path)
public void Append(ApiEntries entries, bool replace = false)
{ {
if (entries == null) return; if (string.IsNullOrEmpty(path)) return null;
var key = path.ToLower();
lock (locker) lock (locker)
{ {
var dict = Applications ?? new Dictionary<string, ApiApplication>(); if (_actions.TryGetValue(key, out var value)) return value;
foreach (var app in entries.Applications)
{
var key = app.Key;
if (dict.ContainsKey(key))
{
if (replace) dict[key] = app.Value;
continue;
}
dict.Add(app.Key, app.Value);
}
var list = new List<ApiApplication>(dict.Values);
list.Sort(new Comparison<ApiApplication>((a, b) => a.Lower.CompareTo(b.Lower)));
Applications = dict;
Items = list;
} }
return null;
} }
#endregion /// <summary></summary>
public ApiApplication[] Applications { get => _apps.Values.Map(x => x); }
#region 静态方法。 /// <summary></summary>
public ApiAction[] Actions { get => _actions.Values.Map(x => x); }
/// <summary>从指定的程序集获取入口。</summary> /// <summary></summary>
public static ApiEntries From(Assembly assembly) public ApiEntries() { }
{
if (assembly == null) return null;
var types = RuntimeUtility.GetTypes(assembly, false);
var dict = new Dictionary<string, ApiApplication>();
foreach (var type in types)
{
var app = Application(type, true);
if (app == null) continue;
var lower = app.Lower;
if (dict.ContainsKey(lower)) continue;
dict.Add(lower, app);
var funcs = new Dictionary<string, ApiFunction>();
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public);
foreach (var method in methods)
{
var func = Function(app, method);
if (func == null) continue;
if (funcs.ContainsKey(func.Lower)) continue;
funcs.Add(func.Lower, func);
}
app.Functions = funcs;
app.Items = new List<ApiFunction>(funcs.Values);
app.Items.Sort(new Comparison<ApiFunction>((a, b) => a.Name.CompareTo(b.Name)));
}
var entries = new ApiEntries();
entries.Applications = dict;
var list = new List<ApiApplication>(dict.Values);
list.Sort(new Comparison<ApiApplication>((a, b) => a.Lower.CompareTo(b.Lower)));
entries.Items = list;
return entries;
}
/// <summary>从多个程序集中获取入口。</summary> /// <summary></summary>
public static ApiEntries From(IEnumerable<Assembly> assemblies, bool replace = false) public ApiEntries(IEnumerable<ApiApplication> applications, IEnumerable<ApiAction> actions, bool replace = false) : this()
{ {
if (assemblies == null) return null; Add(applications, replace);
var entries = new ApiEntries(); Add(actions, replace);
foreach (var assembly in assemblies) entries.Append(From(assembly), replace);
return entries;
} }
/// <summary>从当前程序中获取入口。 </summary> /// <summary></summary>
public static ApiEntries Calling() => From(Assembly.GetCallingAssembly()); public ApiEntries(IEnumerable<ApiApplication> applications, bool replace = false) : this(applications, null, replace) { }
/// <summary>从当前 AppDomain 中获取入口。</summary> /// <summary></summary>
public static ApiEntries AppDomain(bool replace = false) => From(System.AppDomain.CurrentDomain.GetAssemblies(), replace); public ApiEntries(ApiAction[] actions, bool replace = false) : this(null, actions, replace) { }
static ApiApplication Application(Type type, bool requireAttribute) /// <summary>添加入口。</summary>
public void Add(IEnumerable<ApiApplication> applications, bool replace = false)
{ {
if (type == null) return null; if (applications == null) return;
lock (locker)
// 检查类型的属性。
if (!type.IsClass) return null;
if (type.IsAbstract) return null;
if (type.IsGenericType) return null;
if (type.GetGenericArguments().NotEmpty()) return null;
if (!RuntimeUtility.CanNew(type)) return null;
// 检查类型的特性。
var apis = type.GetCustomAttributes(typeof(ApiAttribute), false);
var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
if (requireAttribute && api == null) return null;
// 检查基类。
if (!RuntimeUtility.IsInherits(type, typeof(ApiController))) return null;
// Entry
var entry = new ApiApplication();
entry.Type = type;
entry.Attribute = api;
// Attribute
if (api != null)
{ {
entry.Name = string.IsNullOrEmpty(api.Name) ? type.Name : api.Name; foreach (var app in applications)
entry.Lower = entry.Name.ToLower(); {
var name = api.Name; if (app == null) continue;
if (string.IsNullOrEmpty(name)) name = type.Name;
entry.Caption = api.Caption; var appKey = app.Name.Lower();
entry.Description = api.Description; if (appKey.IsEmpty()) continue;
}
else
{
entry.Name = type.Name;
entry.Lower = entry.Name.ToLower();
entry.Caption = null;
entry.Description = null;
entry.Hidden = true;
}
// Caption if (_apps.ContainsKey(appKey))
if (string.IsNullOrEmpty(entry.Caption)) {
{ if (replace) _apps[appKey] = app;
var captions = type.GetCustomAttributes(typeof(CaptionAttribute), true); }
if (captions.Length > 0) else
{ {
var caption = (CaptionAttribute)captions[0]; _apps.Add(appKey, app);
entry.Caption = caption.Title; }
entry.Description = caption.Description;
} }
} }
// Hidden
if (type.Contains<HiddenAttribute>(false)) entry.Hidden = true;
// Independent
if (type.Contains<IndependentAttribute>(false)) entry.Independent = true;
// Module
var assemblyName = type.Assembly.GetName();
entry.Module = TextUtility.Join("-", assemblyName.Name, assemblyName.Version.ToString());
return entry;
} }
static ApiFunction Function(ApiApplication application, MethodInfo method) /// <summary>添加入口。</summary>
public void Add(IEnumerable<ApiAction> actions, bool replace = false)
{ {
if (application == null) return null; if (actions == null) return;
if (method == null) return null; lock (locker)
// 滤除构造函数、抽象方法、泛型和非本类定义方法。
if (method.IsConstructor) return null;
if (method.IsAbstract) return null;
if (method.GetGenericArguments().NotEmpty()) return null;
// 滤除 get 和 set 访问器。
var methodName = method.Name;
if (methodName.StartsWith("get_") || methodName.StartsWith("set_")) return null;
// 定义者。
var declaring = method.DeclaringType;
if (declaring.Equals(typeof(object))) return null;
// 检查 ApiAttribute 特性。
var apis = method.GetCustomAttributes(typeof(ApiAttribute), false);
var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
// Entry
var entry = new ApiFunction();
entry.Application = application;
entry.Method = method;
// 返回值。
var returnable = method.ReturnType;
if (returnable.Equals(typeof(void))) returnable = null;
entry.Returnable = returnable;
// 参数。
var pis = method.GetParameters();
if (pis != null && pis.Length > 0)
{ {
var pisc = pis.Length; foreach (var action in actions)
for (var i = 0; i < pisc; i++)
{ {
var pi = pis[i]; if (action == null) continue;
if (pi.IsIn) return null;
if (pi.IsOut) return null;
}
entry.Parameters = pis;
if (pisc == 1) var actionKey = action.Path.Lower();
{ if (actionKey.IsEmpty()) continue;
var pi = pis[0];
var pt = pi.ParameterType; if (_actions.ContainsKey(actionKey))
if (RuntimeUtility.IsInherits(pt, typeof(Source.Record))) entry.ParamIsRecord = true; {
if (replace) _actions[actionKey] = action;
}
else
{
_actions.Add(actionKey, action);
}
} }
} }
}
if (api != null) /// <summary>追加指定的集合,指定 replace 参数将替换当前实例中的同名的入口。</summary>
public void Add(ApiEntries entries, bool replace = false)
{
if (entries == null) return;
Add(entries.Applications, replace);
Add(entries.Actions, replace);
}
/// <summary>清空当前实例。</summary>
public void Clear()
{
lock (locker)
{ {
entry.Name = string.IsNullOrEmpty(api.Name) ? method.Name : api.Name; _apps.Clear();
entry.Lower = entry.Name.ToLower(); _actions.Clear();
entry.Caption = api.Caption;
entry.Description = api.Description;
} }
else }
/// <summary>生成 Json 实例。</summary>
public Json ToJson()
{
lock (locker)
{ {
entry.Name = method.Name; var obj = new
entry.Lower = entry.Name.ToLower(); {
entry.Caption = null; applications = Applications,
entry.Description = null; actions = Actions
};
return Json.From(obj);
} }
entry.Name = method.Name; }
entry.Lower = entry.Name.ToLower();
#endregion
#region static
/// <summary>从指定的程序集获取入口。</summary>
public static ApiEntries From(Assembly assembly, bool replace = false)
{
if (assembly == null) return null;
// Caption var apps = new List<ApiApplication>();
var captions = method.GetCustomAttributes(typeof(CaptionAttribute), true); var actions = new List<ApiAction>();
if (captions.Length > 0) var types = assembly.GetExportedTypes();
foreach (var type in types)
{ {
var caption = (CaptionAttribute)captions[0]; apps.Add(ApiApplication.Parse(type, true));
entry.Caption = caption.Title; actions.AddRange(ApiAction.Parse(type));
entry.Description = caption.Description;
} }
// Hidden var entries = new ApiEntries(apps, actions, replace);
entry.Hidden = application.Hidden; return entries;
if (!entry.Hidden && method.Contains<HiddenAttribute>(false)) entry.Hidden = true; }
return entry; /// <summary>从多个程序集中获取入口。</summary>
public static ApiEntries From(IEnumerable<Assembly> assemblies, bool replace = false)
{
if (assemblies == null) return null;
var entries = new ApiEntries();
foreach (var assembly in assemblies) entries.Add(From(assembly), replace);
return entries;
} }
/// <summary>从当前程序中获取入口。 </summary>
public static ApiEntries Calling(bool replace = false) => From(Assembly.GetCallingAssembly(), replace);
/// <summary>从当前 AppDomain 中获取入口。</summary>
public static ApiEntries AppDomain(bool replace = false) => From(System.AppDomain.CurrentDomain.GetAssemblies(), replace);
#endregion #endregion
} }

200
Apewer/Web/ApiEntry.cs

@ -1,200 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Apewer.Web
{
/// <summary>API 入口。</summary>
[Serializable]
public sealed class ApiEntry : IToJson
{
#region Reflection
internal Assembly _assembly = null;
internal Module _module = null;
internal Type _type = null;
internal MethodInfo _method = null;
/// <summary>定义当前入口的程序集。</summary>
public Assembly Assembly { get => _assembly; }
/// <summary>定义当前入口的模块。</summary>
public Module Module { get => _module; }
/// <summary>定义当前入口的类型。</summary>
public Type Type { get => _type; }
/// <summary>定义当前入口的方法。</summary>
public MethodInfo Method { get => _method; }
#endregion
#region Define
internal CaptionAttribute _caption = null;
internal HiddenAttribute _hidden = null;
internal RoutePrefixAttribute _prefix = null;
internal RouteAttribute _route = null;
internal string _path = null;
internal string _lower = null;
/// <summary>当前入口的路由前缀。</summary>
public RoutePrefixAttribute RoutePrefix { get => _prefix; }
/// <summary>当前入口的路由。</summary>
public RouteAttribute Route { get => _route; }
/// <summary>当前入口的标题。</summary>
public CaptionAttribute Caption { get => _caption; }
/// <summary>当前入口的路由路径。</summary>
public string Path { get => _path; }
#endregion
#region HTTP Method
internal bool _restricted = false;
internal HttpConnectAttribute _connect = null;
internal HttpDeleteAttribute _delete = null;
internal HttpGetAttribute _get = null;
internal HttpHeadAttribute _head = null;
internal HttpOptionsAttribute _options = null;
internal HttpPatchAttribute _patch = null;
internal HttpPostAttribute _post = null;
internal HttpPutAttribute _put = null;
internal HttpTraceAttribute _trace = null;
/// <summary>当前入口拥有 <see cref="HttpConnectAttribute"/> 特性。</summary>
public HttpConnectAttribute Connect { get => _connect; }
/// <summary>当前入口拥有 <see cref="HttpDeleteAttribute"/> 特性。</summary>
public HttpDeleteAttribute Delete { get => _delete; }
/// <summary>当前入口拥有 <see cref="HttpGetAttribute"/> 特性。</summary>
public HttpGetAttribute Get { get => _get; }
/// <summary>当前入口拥有 <see cref="HttpHeadAttribute"/> 特性。</summary>
public HttpHeadAttribute Head { get => _head; }
/// <summary>当前入口拥有 <see cref="HttpOptionsAttribute"/> 特性。</summary>
public HttpOptionsAttribute Options { get => _options; }
/// <summary>当前入口拥有 <see cref="HttpPatchAttribute"/> 特性。</summary>
public HttpPatchAttribute Patch { get => _patch; }
/// <summary>当前入口拥有 <see cref="HttpPostAttribute"/> 特性。</summary>
public HttpPostAttribute Post { get => _post; }
/// <summary>当前入口拥有 <see cref="HttpPutAttribute"/> 特性。</summary>
public HttpPutAttribute Put { get => _put; }
/// <summary>当前入口拥有 <see cref="HttpTraceAttribute"/> 特性。</summary>
public HttpTraceAttribute Trace { get => _trace; }
#endregion
private ApiEntry() { }
internal static ApiEntry[] Parse(Type type)
{
return null;
}
internal static ApiEntry Parse(Type type, MethodInfo method, RoutePrefixAttribute prefix, bool typeIndependent)
{
if (type == null) return null;
if (method == null) return null;
if (!method.IsPublic) return null;
if (method.IsGenericMethod) return null;
if (method.IsStatic) return null;
var route = RuntimeUtility.GetAttribute<RouteAttribute>(method, false);
var path = route == null ? null : Concat(prefix, route);
var lower = path == null ? null : path.ToLower();
var entry = new ApiEntry();
entry._assembly = type.Assembly;
entry._module = type.Module;
entry._type = type;
entry._method = method;
entry._caption = RuntimeUtility.GetAttribute<CaptionAttribute>(method, true);
entry._hidden = RuntimeUtility.GetAttribute<HiddenAttribute>(method, true);
entry._prefix = prefix;
entry._route = route;
entry._path = path;
entry._lower = lower;
// 允许的 HTTP 方法,指定任何方法即表示需要限定方法。
entry._connect = RuntimeUtility.GetAttribute<HttpConnectAttribute>(method, false);
entry._delete = RuntimeUtility.GetAttribute<HttpDeleteAttribute>(method, false);
entry._get = RuntimeUtility.GetAttribute<HttpGetAttribute>(method, false);
entry._head = RuntimeUtility.GetAttribute<HttpHeadAttribute>(method, false);
entry._options = RuntimeUtility.GetAttribute<HttpOptionsAttribute>(method, false);
entry._patch = RuntimeUtility.GetAttribute<HttpPatchAttribute>(method, false);
entry._post = RuntimeUtility.GetAttribute<HttpPostAttribute>(method, false);
entry._put = RuntimeUtility.GetAttribute<HttpPutAttribute>(method, false);
entry._trace = RuntimeUtility.GetAttribute<HttpTraceAttribute>(method, false);
if (entry._get != null) entry._restricted = true;
else if (entry._post != null) entry._restricted = true;
else if (entry._options != null) entry._restricted = true;
else if (entry._connect != null) entry._restricted = true;
else if (entry._delete != null) entry._restricted = true;
else if (entry._head != null) entry._restricted = true;
else if (entry._patch != null) entry._restricted = true;
else if (entry._put != null) entry._restricted = true;
else if (entry._trace != null) entry._restricted = true;
return entry;
}
static string Concat(RoutePrefixAttribute prefix, RouteAttribute route)
{
var segs = new List<string>(16);
if (prefix != null && !string.IsNullOrEmpty(prefix.Path))
{
var split = prefix.Path.Split('/');
var count = split.Length;
for (var i = 0; i < count; i++)
{
var seg = split[i];
if (string.IsNullOrEmpty(seg)) continue;
segs.Add(seg);
}
}
if (route != null && !string.IsNullOrEmpty(route.Path))
{
var split = route.Path.Split('/');
var count = split.Length;
for (var i = 0; i < count; i++)
{
var seg = split[i];
if (string.IsNullOrEmpty(seg)) continue;
segs.Add(seg);
}
}
if (segs.Count < 1) return "/";
return "/" + TextUtility.Join("/", segs.ToArray());
}
/// <summary>生成包含当前实例属性的 Json 对象。</summary>
/// <returns>Json 对象。</returns>
public Json ToJson()
{
var json = Json.NewObject();
return json;
}
}
}

170
Apewer/Web/ApiFunction.cs

@ -5,61 +5,157 @@ using System.Reflection;
namespace Apewer.Web namespace Apewer.Web
{ {
internal sealed class ApiFunction /// <summary></summary>
public sealed class ApiFunction : IToJson
{ {
internal ApiApplication Application; #region fields
internal MethodInfo Method;
internal Type Returnable;
internal ParameterInfo[] Parameters;
internal bool ParamIsRecord = false;
// 主特性和主要属性。 ApiApplication _application = null;
// internal ApiAttribute Attribute; MethodInfo _method = null;
internal string Name = null; Type _return = null;
internal string Lower = null; ApiParameter[] _parameters = null;
// 附加特性。 string _name = null;
internal bool Hidden; string _lower = null;
internal string Caption; string _caption = null;
internal string Description; string _description = null;
private Class<Json> psJson = null; bool _hidden = false;
internal Json ToJson(ApiOptions options) #endregion
#region properties
/// <summary></summary>
public ApiApplication Application { get => _application; }
/// <summary>方法。</summary>
public MethodInfo Method { get => _method; }
/// <summary>参数。</summary>
public ApiParameter[] Parameters { get => _parameters.Map(x => x); }
/// <summary>返回的类型。</summary>
public Type ReturnType { get => _return; }
/// <summary></summary>
public string Name { get => _name; }
/// <summary></summary>
public string Caption { get => _caption; }
/// <summary></summary>
public string Description { get => _description; }
/// <summary></summary>
public bool Hidden { get => _hidden; }
#endregion
/// <summary></summary>
public Json ToJson() => ToJson(true);
/// <summary></summary>
public Json ToJson(ApiOptions options) => ToJson((options ?? new ApiOptions()).WithParameters);
/// <summary></summary>
public Json ToJson(bool withParameters)
{ {
if (Hidden) return null; if (Hidden) return null;
var json = Json.NewObject(); var json = Json.NewObject();
json.SetProperty("name", Name); json.SetProperty("name", Name);
if (!string.IsNullOrEmpty(Caption)) json.SetProperty("caption", Caption); if (!string.IsNullOrEmpty(Caption)) json.SetProperty("caption", Caption);
if (!string.IsNullOrEmpty(Description)) json.SetProperty("description", Description); if (!string.IsNullOrEmpty(Description)) json.SetProperty("description", Description);
if (options.WithParameters) if (withParameters) json.SetProperty("parameters", Json.From(_parameters));
return json;
}
ApiFunction(MethodInfo method, ApiApplication application, ApiParameter[] parameters)
{
// 检查 ApiAttribute 特性。
var apis = method.GetCustomAttributes(typeof(ApiAttribute), false);
var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
// Entry
_application = application;
_method = method;
// 返回值。
_return = method.ReturnType;
if (_return.Equals(typeof(void))) _return = null;
// api
if (api == null)
{ {
if (psJson == null) _name = method?.Name;
_lower = _name?.Lower();
}
else
{
_name = string.IsNullOrEmpty(api.Name) ? method?.Name : api.Name;
_lower = _name?.Lower();
_caption = api.Caption;
_description = api.Description;
}
// caption
if (string.IsNullOrEmpty(_caption))
{
var captions = method.GetCustomAttributes(typeof(CaptionAttribute), true);
if (captions.Length > 0)
{ {
if (Parameters == null || Parameters.Length < 0) var caption = (CaptionAttribute)captions[0];
{ _caption = caption.Title;
psJson = new Class<Json>(); if (string.IsNullOrEmpty(_description))
}
else
{ {
var ps = Json.NewArray(); _description = caption.Description;
var psList = new List<ParameterInfo>();
psList.AddRange(Parameters);
psList.Sort(new Comparison<ParameterInfo>((a, b) => a.Name.CompareTo(b.Name)));
foreach (var pi in psList)
{
var p = Json.NewObject();
p.SetProperty("name", pi.Name);
p.SetProperty("type", pi.ParameterType.Name);
ps.AddItem(p);
}
psJson = new Class<Json>(ps);
} }
} }
if (psJson) json.SetProperty("parameters", psJson.Value);
} }
return json;
// hidden
if (method.Contains<HiddenAttribute>(false)) _hidden = true;
// 参数。
_parameters = parameters;
}
/// <summary></summary>
public static ApiFunction Parse(ApiApplication application, MethodInfo method)
{
if (application == null) return null;
if (method == null) return null;
// 滤除构造函数、抽象方法、泛型和非本类定义方法。
if (method.IsConstructor) return null;
if (method.IsAbstract) return null;
if (method.GetGenericArguments().NotEmpty()) return null;
// 滤除 get 和 set 访问器。
var methodName = method.Name;
if (methodName.StartsWith("get_") || methodName.StartsWith("set_")) return null;
switch (methodName)
{
case "Dispose":
return null;
}
// 定义者。
var declaring = method.DeclaringType;
if (declaring.Equals(typeof(object))) return null;
// 参数,所有参数必须是 In 方向。
var ps = new List<ApiParameter>();
foreach (var pi in method.GetParameters())
{
var p = ApiParameter.Parse(pi);
if (p == null) return null;
ps.Add(p);
}
return new ApiFunction(method, application, ps.ToArray());
} }
} }

16
Apewer/Web/ApiInvoker.cs

@ -51,23 +51,27 @@ namespace Apewer.Web
} }
/// <summary>发起调用。</summary> /// <summary>发起调用。</summary>
public string Invoke(ApiProvider provider) /// <exception cref="ArgumentNullException" />
public void Invoke(ApiProvider provider)
{ {
if (provider == null) throw new ArgumentNullException(nameof(provider));
var entries = Entries; var entries = Entries;
if (entries == null) entries = ApiEntries.AppDomain(); if (entries == null) entries = ApiEntries.AppDomain();
Entries = entries; Entries = entries;
return Invoke(provider, entries); Invoke(provider, entries);
} }
/// <summary>发起调用。</summary> /// <summary>发起调用。</summary>
public string Invoke(ApiProvider provider, ApiEntries entries) /// <exception cref="ArgumentNullException" />
public void Invoke(ApiProvider provider, ApiEntries entries)
{ {
if (provider == null) return "未指定有效的服务程序。"; if (provider == null) throw new ArgumentNullException(nameof(provider));
if (entries == null) return "未指定有效的入口。"; if (entries == null) throw new ArgumentNullException(nameof(entries));
var context = new ApiContext(this, provider, entries); var context = new ApiContext(this, provider, entries);
var processor = new ApiProcessor(context); var processor = new ApiProcessor(context);
return processor.Run(); processor.Run();
} }
} }

18
Apewer/Web/ApiMiddleware.cs

@ -1,18 +0,0 @@
#if Middleware
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>中间件。</summary>
public abstract class ApiMiddleware
{
}
}
#endif

233
Apewer/Web/ApiModel.cs

@ -8,19 +8,15 @@ namespace Apewer.Web
{ {
/// <summary>Response 模型。</summary> /// <summary>Response 模型。</summary>
public abstract class ApiModel : IToJson public abstract class ApiModel : IApiModel
{ {
#region #region Headers
private int _expires = 0; int _expires = 0;
StringPairs _headers = new StringPairs();
internal ApiRequest _request; int SafeExpires(int seconds)
internal ApiResponse _response;
internal ApiOptions _options;
internal ApiProvider _provider;
static int SafeExpires(int seconds)
{ {
var s = seconds; var s = seconds;
if (s < 0) s = 0; if (s < 0) s = 0;
@ -28,38 +24,37 @@ namespace Apewer.Web
return s; return s;
} }
#endregion /// <summary>响应缓存的过期时间,以秒为单位。</summary>
public virtual int Expires { get => _expires; set => _expires = SafeExpires(value); }
#region 内部属性和方法。 /// <summary>状态。</summary>
/// <remarks>默认值:200。</remarks>
public virtual int Status { get; set; }
/// <summary>处理当前模型的 API 请求。</summary> /// <summary>设置 Response 头。</summary>
protected ApiRequest Request { get => _request; } public virtual StringPairs Headers { get => _headers; set => _headers = value ?? new StringPairs(); }
/// <summary>处理当前模型的 API 响应。</summary> /// <summary>内容类型。当 Headers 中包含 Content-Type 时此属性将被忽略。</summary>
protected ApiResponse Response { get => Response; } public virtual string ContentType { get; set; }
/// <summary>处理当前模型的 API 选项。</summary> /// <summary>设置文件名,告知客户端此附件处理此响应。</summary>
protected ApiOptions Options { get => _options; } public virtual string Attachment { get; set; }
/// <summary>处理当前模型的服务程序实例。</summary> #endregion
protected ApiProvider Provider { get => _provider; }
/// <summary>在 Response 头中添加用于设置文件名的属性。</summary> #region Output
protected void SetAttachment()
{
if (_provider == null) return;
var name = Attachment;
if (string.IsNullOrEmpty(name)) return;
var encoded = TextUtility.EncodeUrl(name);
_provider.SetHeader("Content-Disposition", $"attachment; filename={encoded}");
}
private List<string> WriteHeader() /// <summary>执行输出。</summary>
{ /// <remarks>此方法由 API 调用器发起调用,用户程序不应主动调用。</remarks>
if (_provider == null) return null; /// <exception cref="InvalidOperationException"></exception>
public abstract void Output(ApiContext context);
/// <summary>向 HTTP 写入头。</summary>
/// <returns>已写入的头。</returns>
List<string> WriteHeaders(ApiContext context)
{
var status = Status > 0 ? Status : 200; var status = Status > 0 ? Status : 200;
if (status != 200) _provider.SetStatus(status); if (status != 200) context.Provider.SetStatus(status);
var headers = Headers; var headers = Headers;
var added = new List<string>(32); var added = new List<string>(32);
@ -69,69 +64,82 @@ namespace Apewer.Web
{ {
if (header.Key.IsEmpty()) continue; if (header.Key.IsEmpty()) continue;
if (header.Value.IsEmpty()) continue; if (header.Value.IsEmpty()) continue;
_provider.SetHeader(header.Key, header.Value); context.Provider.SetHeader(header.Key, header.Value);
added.Add(header.Key.Lower()); added.Add(header.Key.Lower());
} }
} }
SetAttachment(); SetAttachment(context);
_provider.SetCache(Expires); context.Provider.SetCache(Expires);
if (!added.Contains("content-type")) _provider.SetContentType(ContentType); if (!added.Contains("content-type")) context.Provider.SetContentType(ContentType);
return added; return added;
} }
/// <summary>以指定参数输出。</summary> /// <summary>在 Response 头中添加用于设置文件名的属性。</summary>
protected void Output(byte[] bytes) void SetAttachment(ApiContext context)
{ {
if (_provider == null) return; var name = Attachment;
if (_provider.PreWrite().NotEmpty()) return; if (string.IsNullOrEmpty(name)) return;
var added = WriteHeader(); var encoded = TextUtility.EncodeUrl(name);
context.Provider.SetHeader("Content-Disposition", $"attachment; filename={encoded}");
}
/// <summary>输出头和响应体,响应体是字节数组。</summary>
/// <exception cref="ArgumentNullException" />
protected void Output(ApiContext context, byte[] bytes)
{
if (context == null) throw new ArgumentNullException(nameof(context));
// 写入头
if (context.Provider == null) return;
if (context.Provider.PreWrite().NotEmpty()) return;
var added = WriteHeaders(context);
// 写入头
var length = bytes == null ? 0 : bytes.Length; var length = bytes == null ? 0 : bytes.Length;
if (!added.Contains("content-length")) _provider.SetContentLength(length); if (!added.Contains("content-length")) context.Provider.SetContentLength(length);
if (length > 0) _provider.ResponseBody().Write(bytes);
_provider.Sent(); // 写入主体
if (length > 0) context.Provider.ResponseBody().Write(bytes);
// 发送
context.Provider.Sent();
} }
/// <summary>以指定参数输出。</summary> /// <summary>以指定参数输出。</summary>
protected void Output(Stream stream, bool dispose) /// <exception cref="ArgumentNullException" />
protected void Output(ApiContext context, Stream stream)
{ {
if (_provider == null) return; if (context == null) throw new ArgumentNullException(nameof(context));
if (_provider.PreWrite().NotEmpty()) return;
var added = WriteHeader();
if (!added.Contains("content-length")) // 写入头
if (context.Provider == null) return;
if (context.Provider.PreWrite().NotEmpty()) return;
var added = WriteHeaders(context);
if (stream == null)
{ {
var length = stream.Length - stream.Position; context.Provider.SetContentLength(0);
_provider.SetContentLength(length); context.Provider.Sent();
} }
_provider.ResponseBody().Write(stream); else
_provider.Sent(); {
if (dispose) RuntimeUtility.Dispose(stream); // 写入头
} if (!added.Contains("content-length"))
{
#endregion var length = stream.Length - stream.Position;
context.Provider.SetContentLength(length);
/// <summary>状态。</summary> }
/// <remarks>默认值:200。</remarks>
public virtual int Status { get; set; }
/// <summary>内容类型。当 Headers 中包含 Content-Type 时此属性将被忽略。</summary>
public virtual string ContentType { get; set; }
/// <summary>响应缓存的过期时间,以秒为单位。</summary>
public virtual int Expires { get => _expires; set => _expires = SafeExpires(value); }
/// <summary>设置文件名,告知客户端此附件处理此响应。</summary> // 写入主体
public virtual string Attachment { get; set; } context.Provider.ResponseBody().Write(stream);
/// <summary>设置 Response 头。</summary> // 发送
public virtual StringPairs Headers { get; set; } context.Provider.Sent();
}
}
/// <summary>执行输出。</summary> #endregion
/// <remarks>此方法由 API 调用器发起调用,用户程序不应主动调用。</remarks>
/// <exception cref="InvalidOperationException"></exception>
public abstract void Output();
/// <summary>创建对象实例,并设置默认属性。</summary> /// <summary>创建对象实例,并设置默认属性。</summary>
public ApiModel() public ApiModel()
@ -143,15 +151,6 @@ namespace Apewer.Web
Headers = new StringPairs(); Headers = new StringPairs();
} }
/// <summary></summary>
public Json ToJson()
{
var json = new Json();
json.SetProperty("status", Status);
json.SetProperty("content-type", ContentType);
return json;
}
} }
/// <summary>输出二进制的 Response 模型。</summary> /// <summary>输出二进制的 Response 模型。</summary>
@ -162,7 +161,7 @@ namespace Apewer.Web
public byte[] Bytes { get; set; } public byte[] Bytes { get; set; }
/// <summary>输出字节数组。</summary> /// <summary>输出字节数组。</summary>
public override void Output() => Output(Bytes); public override void Output(ApiContext context) => Output(context, Bytes);
/// <summary>创建对象实例,并设置默认属性。</summary> /// <summary>创建对象实例,并设置默认属性。</summary>
public ApiBytesModel(byte[] bytes = null, string contentType = "application/octet-stream") public ApiBytesModel(byte[] bytes = null, string contentType = "application/octet-stream")
@ -185,7 +184,17 @@ namespace Apewer.Web
public bool AutoDispose { get; set; } public bool AutoDispose { get; set; }
/// <summary>输出流。</summary> /// <summary>输出流。</summary>
public override void Output() => Output(Stream, AutoDispose); public override void Output(ApiContext context)
{
if (AutoDispose)
{
using (var stream = Stream) Output(context, Stream);
}
else
{
Output(context, Stream);
}
}
/// <summary>当指定 AutoDispose 属性时释放流。</summary> /// <summary>当指定 AutoDispose 属性时释放流。</summary>
public void Dispose() public void Dispose()
@ -220,21 +229,17 @@ namespace Apewer.Web
public string Path { get => _path; set => SetPath(value); } public string Path { get => _path; set => SetPath(value); }
/// <summary>输出指定路径的文件。</summary> /// <summary>输出指定路径的文件。</summary>
public override void Output() public override void Output(ApiContext context)
{ {
try if (!File.Exists(Path)) return;
{
if (!File.Exists(Path)) return;
var info = new FileInfo(Path); var info = new FileInfo(Path);
if (string.IsNullOrEmpty(Attachment)) Attachment = info.Name; if (string.IsNullOrEmpty(Attachment)) Attachment = info.Name;
using (var stream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var stream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read))
{ {
Output(stream, false); Output(context, stream);
}
} }
catch { }
} }
/// <summary></summary> /// <summary></summary>
@ -254,7 +259,7 @@ namespace Apewer.Web
public string Text { get; set; } public string Text { get; set; }
/// <summary>输出文本。</summary> /// <summary>输出文本。</summary>
public override void Output() => Output(TextUtility.Bytes(Text)); public override void Output(ApiContext context) => Output(context, TextUtility.Bytes(Text));
/// <summary>创建对象实例,并设置默认属性。</summary> /// <summary>创建对象实例,并设置默认属性。</summary>
public ApiTextModel(string text = null, string contentType = "text/plain") public ApiTextModel(string text = null, string contentType = "text/plain")
@ -281,15 +286,15 @@ namespace Apewer.Web
public bool Camel { get; set; } public bool Camel { get; set; }
/// <summary>输出文本。</summary> /// <summary>输出文本。</summary>
public override void Output() public override void Output(ApiContext context)
{ {
var json = (Json != null && Json.Available) ? Json : Json.NewObject(); var json = (Json != null && Json.Available) ? Json : Json.NewObject();
if (Camel) Json.Camel(json); if (Camel) Json.Camel(json);
Output(TextUtility.Bytes(json.ToString(Indented))); Output(context, TextUtility.Bytes(json.ToString(Indented)));
} }
/// <summary>创建对象实例,并设置默认属性。</summary> /// <summary>创建对象实例,并设置默认属性。</summary>
public ApiJsonModel(Json json = null, bool camel = false, bool indented = true) public ApiJsonModel(Json json = null, bool indented = false, bool camel = true)
{ {
ContentType = "application/json"; ContentType = "application/json";
Camel = camel; Camel = camel;
@ -304,20 +309,21 @@ namespace Apewer.Web
{ {
/// <summary>将要重定向的位置。</summary> /// <summary>将要重定向的位置。</summary>
public string Location { get; set; } public string Location { get; private set; }
/// <summary>执行重定向。</summary> /// <summary>执行重定向。</summary>
public override void Output() public override void Output(ApiContext context)
{ {
var location = Location; var location = Location;
if (string.IsNullOrEmpty(location)) return; if (string.IsNullOrEmpty(location)) return;
if (Provider == null) return; context.Provider.SetRedirect(Location);
Provider.SetRedirect(Location);
} }
/// <summary>重定向到指定的 URL。</summary> /// <summary>重定向到指定的 URL。</summary>
public ApiRedirectModel(string location = null) /// <exception cref="ArgumentNullException" />
public ApiRedirectModel(string location)
{ {
if (location.IsEmpty()) throw new ArgumentNullException(nameof(location));
Location = location; Location = location;
} }
@ -331,23 +337,24 @@ namespace Apewer.Web
public Exception Exception { get; set; } public Exception Exception { get; set; }
/// <summary>解析 Exception 的内容并输出。</summary> /// <summary>解析 Exception 的内容并输出。</summary>
public override void Output() public override void Output(ApiContext context)
{ {
Status = 500; Status = 500;
ContentType = "text/plain"; ContentType = "text/plain";
Output(ToString().Bytes()); Output(context, Format(Exception).Bytes());
} }
/// <summary></summary> /// <summary></summary>
public ApiExceptionModel(Exception exception = null) /// <exception cref="ArgumentNullException" />
public ApiExceptionModel(Exception exception)
{ {
if (exception == null) throw new ArgumentNullException(nameof(exception));
Exception = exception; Exception = exception;
} }
/// <summary></summary> /// <summary></summary>
public override string ToString() static string Format(Exception ex)
{ {
var ex = Exception;
var sb = new StringBuilder(); var sb = new StringBuilder();
if (ex == null) if (ex == null)
{ {
@ -357,7 +364,7 @@ namespace Apewer.Web
{ {
try try
{ {
sb.Append(Exception.GetType().FullName); sb.Append(ex.GetType().FullName);
var props = ex.GetType().GetProperties(); var props = ex.GetType().GetProperties();
foreach (var prop in props) foreach (var prop in props)
@ -399,7 +406,7 @@ namespace Apewer.Web
public byte[] Bytes { get; set; } public byte[] Bytes { get; set; }
/// <summary>执行重定向。</summary> /// <summary>执行重定向。</summary>
public override void Output() => Output(Bytes); public override void Output(ApiContext context) => Output(context, Bytes);
/// <summary></summary> /// <summary></summary>
public ApiStatusModel(int status = 200) => Status = status; public ApiStatusModel(int status = 200) => Status = status;

16
Apewer/Web/ApiOptions.cs

@ -38,6 +38,10 @@ namespace Apewer.Web
// /// </remarks> // /// </remarks>
// public bool AllowSynchronousIO { get; set; } = true; // public bool AllowSynchronousIO { get; set; } = true;
/// <summary>默认的结果渲染器。</summary>
/// <remarks>默认值:NULL</remarks>
public Action<ApiContext, object> DefaultRenderer { get; set; }
/// <summary>允许输出的 Json 对象缩进。</summary> /// <summary>允许输出的 Json 对象缩进。</summary>
/// <remarks>默认值:不缩进。</remarks> /// <remarks>默认值:不缩进。</remarks>
public bool JsonIndent { get; set; } = false; public bool JsonIndent { get; set; } = false;
@ -49,10 +53,22 @@ namespace Apewer.Web
/// <summary>输出前的检查。</summary> /// <summary>输出前的检查。</summary>
public ApiPreOutput PreOutput { get; set; } public ApiPreOutput PreOutput { get; set; }
/// <summary>默认的结果渲染器。</summary>
/// <remarks>默认值:<see cref="ApiUtility.Error(ApiContext, string)"/></remarks>
public Action<ApiContext, string> TextRenderer { get; set; } = ApiUtility.Error;
/// <summary>在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。</summary> /// <summary>在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。</summary>
/// <remarks>默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。</remarks> /// <remarks>默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。</remarks>
public bool UpgradeHttps { get; set; } = false; public bool UpgradeHttps { get; set; } = false;
/// <summary>使用反射。</summary>
/// <remarks>默认值:使用</remarks>
public bool UseReflection { get; set; } = true;
/// <summary>使用路由。</summary>
/// <remarks>默认值:使用</remarks>
public bool UseRoute { get; set; } = true;
/// <summary>在响应中包含 Access-Control 属性。</summary> /// <summary>在响应中包含 Access-Control 属性。</summary>
/// <remarks>默认值:不包含。</remarks> /// <remarks>默认值:不包含。</remarks>
public bool WithAccessControl { get; set; } = false; public bool WithAccessControl { get; set; } = false;

77
Apewer/Web/ApiParameter.cs

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Apewer.Web
{
/// <summary>API 行为的参数。</summary>
public sealed class ApiParameter : IToJson
{
ParameterInfo _parameter = null;
bool _body = false;
bool _query = false;
/// <summary></summary>
public ParameterInfo ParameterInfo { get => _parameter; }
/// <summary></summary>
public string Name { get => _parameter.Name; }
/// <summary></summary>
public Type Type { get => _parameter.ParameterType; }
/// <summary></summary>
public bool FromBody { get => _body; }
/// <summary></summary>
public bool FromQuery { get => _query; }
/// <summary></summary>
public override string ToString() => _parameter.Name;
/// <summary></summary>
public Json ToJson()
{
var format = ApiActionJsonFormat.Default ?? new ApiActionJsonFormat();
var withReflection = format.WithReflection;
return ToJson(withReflection);
}
/// <summary></summary>
public Json ToJson(bool withReflection)
{
var json = Json.NewObject();
json.SetProperty("name", _parameter.Name);
if (withReflection)
{
json.SetProperty("type", _parameter.ParameterType.Name);
}
if (_body) json.SetProperty("fromBody", _body);
if (_query) json.SetProperty("fromQuery", _query);
return json;
}
/// <summary></summary>
/// <exception cref="ArgumentNullException" />
ApiParameter(ParameterInfo parameterInfo)
{
_parameter = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
_query = RuntimeUtility.Contains<FromQueryAttribute>(parameterInfo);
_body = RuntimeUtility.Contains<FromBodyAttribute>(parameterInfo);
}
/// <summary></summary>
public static ApiParameter Parse(ParameterInfo parameter)
{
if (parameter == null) return null;
if (parameter.IsOut) return null;
if (parameter.IsRetval) return null;
return new ApiParameter(parameter);
}
}
}

512
Apewer/Web/ApiProcessor.cs

@ -2,7 +2,7 @@
using Apewer.Source; using Apewer.Source;
using System; using System;
using System.Net; using System.Net;
using System.Reflection;
using static Apewer.Web.ApiUtility; using static Apewer.Web.ApiUtility;
namespace Apewer.Web namespace Apewer.Web
@ -11,67 +11,64 @@ namespace Apewer.Web
internal class ApiProcessor internal class ApiProcessor
{ {
// in
private ApiContext _context = null; private ApiContext _context = null;
// temp internal ApiProcessor(ApiContext context) => _context = context ?? throw new ArgumentNullException(nameof(context));
// private Uri _url = null;
// private HttpMethod _method = HttpMethod.NULL;
// out
// private ApiEntry _entry = null;
// private ApiRequest _request = null;
// private ApiResponse _response = null;
// private ApiController _controller = null;
internal ApiProcessor(ApiContext context) #region prepare
{
if (context == null) throw new ArgumentNullException(nameof(context));
_context = context;
}
/// <summary>执行处理程序,返回错误信息。</summary> /// <summary>执行处理程序,返回错误信息。</summary>
public string Run() public void Run()
{ {
var error = Flow(); var url = null as Uri;
return error; var method = HttpMethod.NULL;
} var response = null as ApiResponse;
string Flow()
{
try try
{ {
// 检查执行的前提条件,获取 Method 和 URL。 // 检查执行的前提条件,获取 Method 和 URL。
Uri url = null;
HttpMethod method = HttpMethod.NULL;
var check = Check(ref method, ref url); var check = Check(ref method, ref url);
if (!string.IsNullOrEmpty(check)) return check; if (!string.IsNullOrEmpty(check))
{
Logger.Internals.Error(typeof(ApiInvoker), check);
return;
}
// 准备请求模型。 // 准备请求模型。
var request = GetRequest(_context.Provider, _context.Options, method, url); var request = GetRequest(_context.Provider, _context.Options, method, url);
_context.Request = request; _context.Request = request;
// 准备响应模型。 // 准备响应模型。
var response = new ApiResponse(); response = new ApiResponse();
response.Random = request.Random; response.Random = request.Random;
response.Application = request.Application; response.Application = request.Application;
response.Function = request.Function; response.Function = request.Function;
_context.Response = response; _context.Response = response;
// 调用 API。 // 调用 API。
var invoke = Invoke(); Invoke();
if (!string.IsNullOrEmpty(invoke)) return invoke;
// 输出。
response.Duration = Duration(_context.Beginning);
Output(_context.Provider, _context.Options, response, request, method);
return null;
} }
catch (Exception ex) catch (Exception ex)
{ {
var message = ex.Message(); var message = ex.Message();
Logger.Internals.Error(typeof(ApiInvoker), message); Logger.Internals.Error(typeof(ApiInvoker), message);
return message; }
finally
{
// 输出。
if (response != null)
{
try
{
response.Duration = Duration(_context.Beginning);
Output(_context.Provider, _context.Options, response, null, method);
}
catch { }
finally
{
RuntimeUtility.Dispose(response.Model);
}
}
} }
} }
@ -79,18 +76,10 @@ namespace Apewer.Web
{ {
var span = DateTime.Now - beginning; var span = DateTime.Now - beginning;
var ms = span.TotalMilliseconds; var ms = span.TotalMilliseconds;
if (ms > 0D) if (ms < 1000) return Math.Round(ms, 0).ToString() + "ms";
{ if (ms < 10000) return Math.Round(ms / 1000, 2).ToString() + "s";
var s = span.TotalMilliseconds / 1000D; if (ms < 60000) return Math.Round(ms / 1000, 1).ToString() + "s";
if (s > 10D) return Math.Round(s, 1).ToString() + "s"; return Math.Round(ms / 1000, 0).ToString() + "s";
if (s > 1D) return Math.Round(s, 2).ToString() + "s";
if (ms > 10D) return Math.Round(ms, 0).ToString() + "ms";
return Math.Round(ms, 1).ToString() + "ms";
}
else
{
return null;
}
} }
string Check(ref HttpMethod method, ref Uri url) string Check(ref HttpMethod method, ref Uri url)
@ -133,18 +122,171 @@ namespace Apewer.Web
} }
// 寻找入口。 // 寻找入口。
string Invoke() void Invoke()
{ {
var appName = _context.Request.Application; // 路由
var application = _context.Entries.Get(appName); if (_context.Options.UseRoute)
Invoke(application); {
var path = _context?.Request?.Url?.AbsolutePath;
var action = _context.Entries.GetAction(path);
if (action != null)
{
Invoke(action);
_context.Response.Duration = Duration(_context.Beginning);
return;
}
}
var duration = Duration(_context.Beginning); // 反射
_context.Response.Duration = duration; if (_context.Options.UseReflection)
{
var appName = _context.Request.Application;
var application = _context.Entries.GetApplication(appName);
Invoke(application);
_context.Response.Duration = Duration(_context.Beginning);
return;
}
return null; // 未匹配到
_context.Response.Duration = Duration(_context.Beginning);
_context.Response.Model = new ApiStatusModel(404);
}
#endregion
#region common
// 创建控制器实例
static ApiController CreateController(Type type, ApiRequest request, ApiResponse response, ApiOptions options)
{
var controller = (ApiController)Activator.CreateInstance(type);
ApiUtility.SetProperties(controller, request, response, options);
return controller;
}
static void Invoke(ApiContext context, MethodInfo method, ApiParameter[] parameters)
{
context.MethodInfo = method;
// 调用。
var parametersValue = ReadParameters(context.Request, parameters);
var controller = context.Controller;
var returnValue = method.Invoke(controller, parametersValue);
// 程序要求停止输出。
var response = context.Response;
if (response.StopReturn) return;
// 已经有了返回模型。
if (response.Model != null) return;
// 没有返回类型。
var returnType = method.ReturnType;
if (returnType == null) return;
// 已明确字符串类型。
if (returnType.Equals(typeof(string)))
{
var textValue = returnValue as string;
var textRenderer = context.Options.TextRenderer;
if (textRenderer != null)
{
textRenderer.Invoke(context, textValue);
return;
}
// 默认视为提示错误
if (!string.IsNullOrEmpty(textValue)) response.Error(textValue);
return;
}
// 已明确 Exception 类型,视为提示错误。
if (returnValue is Exception)
{
ApiUtility.Exception(response, returnValue as Exception);
return;
}
// 已明确 Json 类型。
if (returnValue is Json json)
{
response.Data = json;
return;
}
// 已明确 Model 类型。
if (returnValue is IApiModel model)
{
response.Model = model;
return;
}
// 已明确 Result 类型。
if (returnValue is IActionResult result)
{
response.Model = result;
return;
}
// 类型未知,尝试 ToJson 方法。
if (returnValue is IToJson toJson)
{
response.Data = toJson.ToJson();
return;
}
// 未知返回类型,尝试使用默认渲染器。
var defaultRenderer = context.Options.DefaultRenderer;
if (defaultRenderer != null) defaultRenderer.Invoke(context, returnValue);
}
#endregion
#region route
// 执行 Action。
void Invoke(ApiAction action)
{
var controller = null as ApiController;
try
{
// 准备控制器。
controller = CreateController(action.Type, _context.Request, _context.Response, _context.Options);
// 准备参数。
var parameters = action.Parameters;
var values = ReadParameters(_context.Request, parameters);
// 调用。
_context.Controller = controller;
Invoke(_context, action.MethodInfo, action.Parameters);
}
catch (Exception ex)
{
if (ex.InnerException != null) ex = ex.InnerException;
ApiUtility.Exception(_context.Response, ex, _context.Options.WithException);
var catcher = _context.Invoker.Catcher;
if (catcher != null)
{
try
{
var apiCatch = new ApiCatch(_context, ex);
catcher.Invoke(apiCatch);
}
catch { }
}
}
finally
{
RuntimeUtility.Dispose(controller);
}
} }
#endregion
#region reflection
// 创建控制器。 // 创建控制器。
void Invoke(ApiApplication application) void Invoke(ApiApplication application)
{ {
@ -153,9 +295,6 @@ namespace Apewer.Web
var request = _context.Request; var request = _context.Request;
var response = _context.Response; var response = _context.Response;
var function = null as ApiFunction;
var controller = null as ApiController;
// Application 无效,尝试默认控制器和枚举。 // Application 无效,尝试默认控制器和枚举。
if (application == null) if (application == null)
{ {
@ -163,40 +302,54 @@ namespace Apewer.Web
if (@default == null) if (@default == null)
{ {
// 没有指定默认控制器,尝试枚举。 // 没有指定默认控制器,尝试枚举。
response.Error("Invalid Application"); response.Status = "notfound";
if (options.AllowEnumerate) response.Data = Enumerate(entries.Enumerate(), options); response.Message = "Not Found";
if (options.AllowEnumerate) response.Data = Enumerate(entries.Applications, options);
return; return;
} }
else else
{ {
// 创建默认控制器。 // 创建默认控制器。
try { controller = CreateController(@default, request, response, options); } var controller = null as ApiController;
catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); } try
{
controller = CreateController(@default, request, response, options);
Invoke(controller, application, null, options, request, response);
}
catch (Exception ex)
{
ApiUtility.Exception(response, ex.InnerException ?? ex);
}
finally
{
RuntimeUtility.Dispose(controller);
}
} }
} }
else else
{ {
// 创建控制器时候会填充 Controller.Request 属性,可能导致 Request.Function 被篡改,所以在创建之前获取 Function。 // 创建控制器时候会填充 Controller.Request 属性,可能导致 Request.Function 被篡改,所以在创建之前获取 Function。
function = application.Get(request.Function); var function = application.GetFunction(request.Function);
try { controller = CreateController(application.Type, request, response, options); } var controller = null as ApiController;
catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); } try
{
controller = CreateController(application.Type, request, response, options);
Invoke(controller, application, function, options, request, response);
}
catch (Exception ex)
{
ApiUtility.Exception(response, ex.InnerException ?? ex);
}
finally
{
RuntimeUtility.Dispose(controller);
}
} }
if (controller == null) response.Error("创建控制器实例失败。");
else Invoke(controller, application, function, options, request, response);
RuntimeUtility.Dispose(controller);
} }
// 调用 Function。 // 调用 Function。
void Invoke(ApiController controller, ApiApplication application, ApiFunction function, ApiOptions options, ApiRequest request, ApiResponse response) void Invoke(ApiController controller, ApiApplication application, ApiFunction function, ApiOptions options, ApiRequest request, ApiResponse response)
{ {
// 没有 ApiApplication,使用了 Options 中指定的默认控制器。
if (application == null)
{
application = new ApiApplication();
application.Independent = true;
application.Hidden = true;
}
try try
{ {
// 控制器初始化。 // 控制器初始化。
@ -208,67 +361,8 @@ namespace Apewer.Web
if (function != null) if (function != null)
{ {
// 调用 API,获取返回值。 // 调用 API,获取返回值。
var result = function.Method.Invoke(controller, ReadParameters(request, function)); _context.Controller = controller;
if (response.StopReturn) return; Invoke(_context, function.Method, function.Parameters);
// 检查返回值。
if (result == null || function.Returnable == null) return;
var returnable = function.Returnable;
// 已明确字符串类型,视为提示错误。
if (returnable.Equals(typeof(string)))
{
var error = result as string;
if (!string.IsNullOrEmpty(error)) response.Error(error);
return;
}
// 已明确 Exception 类型,视为提示错误。
if (result is Exception)
{
ApiUtility.Exception(response, result as Exception);
return;
}
// 已明确 Json 类型。
if (result is Json)
{
response.Data = result as Json;
return;
}
// 已明确 Model 类型。
if (result is ApiModel)
{
response.Model = result as ApiModel;
return;
}
// 类型未知,尝试 ToJson 方法。
var tojson = result as IToJson;
if (tojson != null)
{
response.Data = tojson.ToJson();
return;
}
// 类型未知,尝试 Record 模型。
var record = result as IRecord;
if (record != null)
{
response.Data = Json.From(record);
return;
}
// 未知类型,尝试 Json 类型。
var json = result as Json;
if (json != null)
{
response.Data = json;
return;
}
// 未知返回类型,无法明确输出格式,忽略。
} }
else else
{ {
@ -281,44 +375,37 @@ namespace Apewer.Web
} }
// 没有执行任何 Function,尝试枚举。 // 没有执行任何 Function,尝试枚举。
response.Status = "notfound";
if (application.Hidden) if (application.Hidden)
{ {
response.Error("Invalid Application"); response.Message = "Not Found";
} }
else else
{ {
response.Error("Invalid Function"); response.Message = "Not Found";
if (options.AllowEnumerate) response.Data = Enumerate(application.Items, options); if (options.AllowEnumerate) response.Data = Enumerate(application.Functions, options);
} }
} }
} }
catch (Exception exception) catch (Exception ex)
{ {
var ex = exception.InnerException; if (ex.InnerException != null) ex = ex.InnerException;
ApiUtility.Exception(_context.Response, ex, _context.Options.WithException);
var catcher = _context.Invoker.Catcher; var catcher = _context.Invoker.Catcher;
if (catcher != null) if (catcher != null)
{ {
ApiUtility.Exception(response, ex, false);
try try
{ {
var apiCatch = new ApiCatch(controller, options, ex); var apiCatch = new ApiCatch(_context, ex);
catcher.Invoke(apiCatch); catcher.Invoke(apiCatch);
} }
catch { } catch { }
return;
} }
ApiUtility.Exception(response, ex);
} }
} }
static ApiController CreateController(Type type, ApiRequest request, ApiResponse response, ApiOptions options) #endregion
{
var controller = (ApiController)Activator.CreateInstance(type);
ApiUtility.SetProperties(controller, request, response, options);
return controller;
}
#region static #region static
@ -332,7 +419,7 @@ namespace Apewer.Web
// 基本信息。 // 基本信息。
var ip = provider.GetClientIP(); var ip = provider.GetClientIP();
var headers = provider.GetHeaders() ?? new StringPairs(); var headers = provider.GetHeaders() ?? new HttpHeaders();
request.Headers = headers; request.Headers = headers;
request.IP = ip; request.IP = ip;
request.Url = url; request.Url = url;
@ -352,58 +439,62 @@ namespace Apewer.Web
var page = null as string; var page = null as string;
// 解析 POST 请求。 // 解析 POST 请求。
if (request.Method == HttpMethod.POST) switch (request.Method)
{ {
var preRead = provider.PreRead(); case HttpMethod.PATCH:
if (string.IsNullOrEmpty(preRead)) case HttpMethod.POST:
{ case HttpMethod.PUT:
var post = null as byte[]; var preRead = provider.PreRead();
var length = 0L; if (string.IsNullOrEmpty(preRead))
var max = options.MaxRequestBody;
if (max == 0) post = new byte[0];
else if (max < 0) post = provider.RequestBody().Read();
else
{
length = provider.GetContentLength();
if (length <= max) post = provider.RequestBody().Read();
}
length = post == null ? 0 : post.Length;
if (length > 1)
{ {
request.PostData = post; var post = null as byte[];
if (length < 104857600) var length = 0L;
var max = options.MaxRequestBody;
if (max == 0) post = new byte[0];
else if (max < 0) post = provider.RequestBody().Read();
else
{ {
var text = TextUtility.FromBytes(post); length = provider.GetContentLength();
request.PostText = text; if (length <= max) post = provider.RequestBody().Read();
}
// 尝试解析 Json,首尾必须是“{}”或“[]”。 length = post == null ? 0 : post.Length;
var first = post[0]; if (length > 1)
var last = post[length - 1]; {
if ((first == 123 && last == 125) || (first == 91 && last == 93)) request.PostData = post;
if (length < 104857600)
{ {
var json = Json.From(text); var text = TextUtility.FromBytes(post);
if (json != null && json.IsObject) request.PostText = text;
// 尝试解析 Json,首尾必须是“{}”或“[]”。
var first = post[0];
var last = post[length - 1];
if ((first == 123 && last == 125) || (first == 91 && last == 93))
{ {
application = json["application"]; var json = Json.From(text);
function = json["function"]; if (json != null && json.IsObject)
random = json["random"]; {
ticket = json["ticket"]; application = json["application"];
session = json["session"]; function = json["function"];
page = json["page"]; random = json["random"];
ticket = json["ticket"];
var data = json.GetProperty("data"); session = json["session"];
request.PostJson = json; page = json["page"];
request.Data = data ?? Json.NewObject();
var data = json.GetProperty("data");
request.PostJson = json;
request.Data = data ?? Json.NewObject();
}
} }
}
// 尝试解析 Form,需要 application/x-www-form-urlencoded // 尝试解析 Form,需要 application/x-www-form-urlencoded
var contentType = headers.GetValue("content-type", true) ?? ""; var contentType = headers.GetValue("Content-Type") ?? "";
if (contentType.Contains("urlencoded")) request.Form = ApiUtility.Parameters(text); if (contentType.Contains("urlencoded")) request.Form = ApiUtility.Parameters(text);
}
} }
} }
} break;
} }
// 解析 URL 参数。 // 解析 URL 参数。
@ -501,7 +592,7 @@ namespace Apewer.Web
{ {
foreach (var header in headers) foreach (var header in headers)
{ {
var key = TextUtility.Trim(header.Key); var key = TextUtility.Trim(header.Name);
if (string.IsNullOrEmpty(key)) continue; if (string.IsNullOrEmpty(key)) continue;
var value = header.Value; var value = header.Value;
if (string.IsNullOrEmpty(value)) continue; if (string.IsNullOrEmpty(value)) continue;
@ -512,8 +603,6 @@ namespace Apewer.Web
return merged; return merged;
} }
internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes) internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes)
{ {
var preWrite = provider.PreWrite(); var preWrite = provider.PreWrite();
@ -587,15 +676,36 @@ namespace Apewer.Web
// 设置头。 // 设置头。
var headers = PrepareHeaders(options, response, request); var headers = PrepareHeaders(options, response, request);
foreach (var header in headers) provider.SetHeader(header.Key, header.Value); foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
var model = response.Model;
// 自定义模型
var model = response.Model as IApiModel;
var result = response.Model as IActionResult;
if (model != null) if (model != null)
{ {
ApiUtility.Initialize(model, request, response, options, provider); try
try { model.Output(); } {
catch (Exception ex) { Logger.Internals.Exception(model, ex); } model.Output(_context);
}
catch (Exception ex)
{
Logger.Internals.Exception(model, ex);
}
RuntimeUtility.Dispose(model); RuntimeUtility.Dispose(model);
return; return;
} }
else if (result != null)
{
try
{
result.ExecuteResult(_context);
}
catch (Exception ex)
{
Logger.Internals.Exception(result, ex);
}
RuntimeUtility.Dispose(result);
return;
}
var text = ApiUtility.ToJson(response, options); var text = ApiUtility.ToJson(response, options);
var bytes = TextUtility.Bytes(text); var bytes = TextUtility.Bytes(text);

11
Apewer/Web/ApiProvider.cs

@ -51,7 +51,7 @@ namespace Apewer.Web
public abstract string GetReferrer(); public abstract string GetReferrer();
/// <summary>获取请求的头。</summary> /// <summary>获取请求的头。</summary>
public abstract StringPairs GetHeaders(); public abstract HttpHeaders GetHeaders();
/// <summary>获取请求的内容类型。</summary> /// <summary>获取请求的内容类型。</summary>
public abstract string GetContentType(); public abstract string GetContentType();
@ -91,4 +91,13 @@ namespace Apewer.Web
} }
/// <summary>API 服务程序。</summary>
public abstract class ApiProvider<TContext> : ApiProvider
{
/// <summary>HttpContext</summary>
public abstract TContext Context { get; }
}
} }

5
Apewer/Web/ApiRequest.cs

@ -14,6 +14,9 @@ namespace Apewer.Web
private Json _data = null; private Json _data = null;
internal string[] _segmentals = null; internal string[] _segmentals = null;
/// <summary>自定义标签。</summary>
public object Tag { get; set; }
#region http #region http
/// <summary>客户端 IP 地址。</summary> /// <summary>客户端 IP 地址。</summary>
@ -35,7 +38,7 @@ namespace Apewer.Web
public StringPairs Parameters { get; set; } = new StringPairs(); public StringPairs Parameters { get; set; } = new StringPairs();
/// <summary>HTTP 头。</summary> /// <summary>HTTP 头。</summary>
public StringPairs Headers { get; set; } = new StringPairs(); public HttpHeaders Headers { get; set; } = new HttpHeaders();
/// <summary>Cookies。</summary> /// <summary>Cookies。</summary>
public CookieCollection Cookies { get; set; } = new CookieCollection(); public CookieCollection Cookies { get; set; } = new CookieCollection();

13
Apewer/Web/ApiResponse.cs

@ -1,4 +1,5 @@
using Apewer.Models; using Apewer.Models;
using Apewer.Network;
using System; using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
@ -11,9 +12,12 @@ namespace Apewer.Web
public sealed class ApiResponse public sealed class ApiResponse
{ {
/// <summary>自定义标签。</summary>
public object Tag { get; set; }
#region internal #region internal
private ApiModel _model = null; private object _model = null;
private Json _data = Json.NewObject(); private Json _data = Json.NewObject();
internal bool StopReturn = false; internal bool StopReturn = false;
@ -35,20 +39,19 @@ namespace Apewer.Web
#region user #region user
/// <summary>头。</summary> /// <summary>头。</summary>
public StringPairs Headers { get; set; } = new StringPairs(); public HttpHeaders Headers { get; set; } = new HttpHeaders();
/// <summary>Cookies。</summary> /// <summary>Cookies。</summary>
public CookieCollection Cookies { get; set; } = new CookieCollection(); public CookieCollection Cookies { get; set; } = new CookieCollection();
/// <summary>获取或设置输出模型。</summary> /// <summary>获取或设置输出模型。</summary>
public ApiModel Model public object Model
{ {
get { return _model; } get { return _model; }
set set
{ {
var old = _model; RuntimeUtility.Dispose(_model);
_model = value; _model = value;
RuntimeUtility.Dispose(old);
} }
} }

92
Apewer/Web/ApiServiceDescriptor.cs

@ -1,92 +0,0 @@
#if Middleware
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>API 服务描述。</summary>
public sealed class ApiServiceDescriptor : IToJson
{
/// <summary>订阅器的生命周期。</summary>
public ApiServiceLifetime Lifetime { get; private set; }
/// <summary>服务的类型。</summary>
public Type ServiceType { get; private set; }
/// <summary>实现服务的类型。</summary>
public Type ImplementationType { get; private set; }
public object ImplementationInstance
{
get;
}
public Func<IServiceProvider, object> ImplementationFactory
{
get;
}
/// <summary>创建订阅器的实例。</summary>
/// <param name="invoker">API 调用器。</param>
/// <param name="lifetime">订阅器的生命周期。</param>
/// <param name="service">服务的类型。</param>
/// <param name="implementation">实现服务的类型。</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentException" />
internal ApiServiceDescriptor(ApiServiceLifetime lifetime, Type service, Type implementation)
{
if (invoker == null) throw new ArgumentNullException(nameof(invoker));
if (service == null) throw new ArgumentNullException(nameof(service));
if (implementation == null) throw new ArgumentNullException(nameof(implementation));
if (!service.IsAssignableFrom(implementation)) throw new ArgumentException($"类型 {implementation.Name} 未实现服务。");
if (!implementation.IsClass) throw new ArgumentException($"实现服务的类型 {implementation.Name} 不是引用类型。");
if (implementation.IsAbstract) throw new ArgumentException($"实现服务的类型 {implementation.Name} 是抽象类型,无法实例化。");
Lifetime = lifetime;
ServiceType = service;
ImplementationType = implementation;
}
/// <summary>创建订阅器的实例。</summary>
/// <param name="invoker">API 调用器。</param>
/// <param name="lifetime">订阅器的生命周期。</param>
/// <param name="service">服务的类型。</param>
/// <param name="implementation">实现服务的方法。</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentException" />
internal ApiServiceDescriptor(ApiServiceLifetime lifetime, Type service, Func<Type, implementation)
{
if (invoker == null) throw new ArgumentNullException(nameof(invoker));
if (service == null) throw new ArgumentNullException(nameof(service));
if (implementation == null) throw new ArgumentNullException(nameof(implementation));
if (!service.IsAssignableFrom(implementation)) throw new ArgumentException($"类型 {implementation.Name} 未实现服务。");
if (!implementation.IsClass) throw new ArgumentException($"实现服务的类型 {implementation.Name} 不是引用类型。");
if (implementation.IsAbstract) throw new ArgumentException($"实现服务的类型 {implementation.Name} 是抽象类型,无法实例化。");
Invoker = invoker;
Lifetime = lifetime;
ServiceType = service;
ImplementationType = implementation;
}
/// <summary>生成 JSON 实例。</summary>
public Json ToJson()
{
var json = new Json();
json["Lifetime"] = Lifetime.ToString();
json["Service"] = ServiceType.FullName;
json["Implementation"] = Implementation.FullName;
return json;
}
}
}
#endif

23
Apewer/Web/ApiServiceLifetime.cs

@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>服务的生命周期。</summary>
public enum ApiServiceLifetime
{
/// <summary>对所有请求使用同一个实例。</summary>
Singleton,
/// <summary>对每个请求使用同一个实例。</summary>
Scoped,
/// <summary>对每个请求里的每次声明创建新实例。</summary>
Transient
}
}

169
Apewer/Web/ApiUtility.cs

@ -117,7 +117,7 @@ namespace Apewer.Web
} }
/// <summary>获取 User Agent。</summary> /// <summary>获取 User Agent。</summary>
public static string UserAgent(StringPairs headers) => headers == null ? null : headers.GetValue("user-agent"); public static string UserAgent(HttpHeaders headers) => headers == null ? null : headers.GetValue("user-agent");
// 从 Uri 对象中解析路径片段。 // 从 Uri 对象中解析路径片段。
private static string[] Segmentals(Uri url) private static string[] Segmentals(Uri url)
@ -162,11 +162,11 @@ namespace Apewer.Web
} }
/// <summary>获取 X-Forwarded-For,不存在时返回 NULL 值。</summary> /// <summary>获取 X-Forwarded-For,不存在时返回 NULL 值。</summary>
public static string[] GetForwardedIP(StringPairs headers) public static string[] GetForwardedIP(HttpHeaders headers)
{ {
if (headers != null) if (headers != null)
{ {
var value = headers.GetValue("x-forwarded-for", true); var value = headers.GetValue("x-forwarded-for");
if (!string.IsNullOrEmpty(value)) if (!string.IsNullOrEmpty(value))
{ {
var fips = new List<string>(); var fips = new List<string>();
@ -349,12 +349,12 @@ namespace Apewer.Web
} }
} }
internal static CookieCollection ParseCookies(StringPairs headers) internal static CookieCollection ParseCookies(HttpHeaders headers)
{ {
var cookies = new CookieCollection(); var cookies = new CookieCollection();
if (headers == null) return cookies; if (headers == null) return cookies;
var hvs = headers.GetValues("cookie", true, false); var hvs = headers.GetValues("Cookie");
foreach (var hv in hvs) foreach (var hv in hvs)
{ {
if (string.IsNullOrEmpty(hv)) continue; if (string.IsNullOrEmpty(hv)) continue;
@ -534,6 +534,25 @@ namespace Apewer.Web
#endregion #endregion
#region ApiContext
/// <summary>作为纯文本输出。</summary>
/// <remarks>Content-Type: text/plain</remarks>
public static void Text(ApiContext context, string text)
{
if (context == null) return;
Model(context.Response, new ApiTextModel(text, "text/plain"));
}
/// <summary>设置 status 为 error,并设置 message 的内容。</summary>
public static void Error(ApiContext context, string text)
{
if (context == null) return;
Error(context.Response, text);
}
#endregion
#region ApiRequest #region ApiRequest
/// <summary>获取 URL 路径段,不存在的段为 NULL 值。可要求解码。</summary> /// <summary>获取 URL 路径段,不存在的段为 NULL 值。可要求解码。</summary>
@ -707,22 +726,24 @@ namespace Apewer.Web
if (response == null) return; if (response == null) return;
response.Model = null; response.Model = null;
response.Status = "exception"; response.Status = "exception";
if (exception == null) return;
try if (exception != null)
{ {
if (setData) try
{ {
var json = ToJson(exception); if (setData)
response.Message = json["message"]; {
response.Data = json; var json = ToJson(exception);
} response.Message = json["message"];
else response.Data = json;
{ }
response.Message = exception.Message(); else
{
response.Message = exception.Message();
}
} }
catch { }
} }
catch { }
} }
private static Json ToJson(Exception exception, bool withInner = true) private static Json ToJson(Exception exception, bool withInner = true)
@ -810,23 +831,24 @@ namespace Apewer.Web
#endregion #endregion
#region ApiModel #region ApiResult
/// <summary>初始化 ApiMode 的属性。</summary> /// <summary>对 HTTP 结果设置文件名。</summary>
public static void Initialize(ApiModel model, ApiRequest request, ApiResponse response, ApiOptions options, ApiProvider provider) /// <param name="result">结果。</param>
/// <param name="name">文件名(未编码)。</param>
public static void SetAttachemnt(this HeadResult result, string name)
{ {
if (model == null) return; if (result == null) throw new ArgumentNullException(nameof(result));
model._request = request; if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
model._response = response;
model._options = options; var encoded = TextUtility.EncodeUrl(name);
model._provider = provider; result.Headers.Add("Content-Disposition", $"attachment; filename={encoded}");
} }
#endregion #endregion
#region ApiFunction Parameters #region ApiFunction Parameters
internal static object[] ReadParameters(ApiRequest request, ApiFunction function) internal static object[] ReadParameters(ApiRequest request, ApiFunction function)
{ {
if (request == null || function == null) return null; if (request == null || function == null) return null;
@ -840,31 +862,41 @@ namespace Apewer.Web
public static object[] ReadParameters(ApiRequest request, ParameterInfo[] parameters) public static object[] ReadParameters(ApiRequest request, ParameterInfo[] parameters)
{ {
if (request == null || parameters == null || parameters.Length < 1) return null; if (request == null || parameters == null || parameters.Length < 1) return null;
var apiParameters = parameters.Map(x => ApiParameter.Parse(x) ?? throw new Exception($"参数【{x.Name}】无效。"));
return ReadParameters(request, apiParameters);
}
var pis = parameters; /// <summary>为带有形参的 Function 准备实参。</summary>
if (pis == null) return null; /// <param name="request">API 请求模型。</param>
/// <param name="parameters">Function 的参数信息。</param>
/// <returns>实参。</returns>
public static object[] ReadParameters(ApiRequest request, ApiParameter[] parameters)
{
if (request == null || parameters == null || parameters.Length < 1) return null;
if (parameters == null) return null;
var count = pis.Length; var count = parameters.Length;
if (count < 1) return null; if (count < 1) return null;
// 当 Function 仅有一个参数时,尝试生成模型。 // 当 Function 仅有一个参数时,尝试生成模型。
if (count == 1) if (count == 1 && parameters[0] != null)
{ {
var pin = pis[0].Name; var parameterName = parameters[0].Name;
var pit = pis[0].ParameterType; var parameterType = parameters[0].Type;
// POST // POST
if (request.Method == HttpMethod.POST) if (request.Method == HttpMethod.POST)
{ {
// string // string
if (pit.Equals(typeof(string))) return new object[] { request.Parameters.GetValue(pin, true) }; if (parameterType.Equals(typeof(string))) return new object[] { request.Parameters.GetValue(parameterName, true) };
// json // json
if (pit.Equals(typeof(Json))) return new object[] { request.PostJson }; if (parameterType.Equals(typeof(Json))) return new object[] { request.PostJson };
#if !NET20 #if !NET20
// dynamic // dynamic
if (pit.Equals(typeof(object))) if (parameterType.Equals(typeof(object)))
{ {
try try
{ {
@ -902,15 +934,13 @@ namespace Apewer.Web
#endif #endif
// class // class
if (pit.IsClass) if (parameterType.IsClass)
{ {
try try
{ {
var entity = Activator.CreateInstance(pit); var entity = ReadParameter(request.Data, parameterType);
var setted = false; if (entity == null) entity = ReadParameter(request.PostJson, parameterType);
if (!setted) setted = ReadParameter(request.Data, entity); if (entity != null) return new object[] { entity.Value };
if (!setted) setted = ReadParameter(request.PostJson, entity);
return new object[] { entity };
} }
catch { } catch { }
} }
@ -923,42 +953,51 @@ namespace Apewer.Web
else else
{ {
// string // string
if (pit.Equals(typeof(string))) return new object[] { request.Parameters.GetValue(pin, true) }; if (parameterType.Equals(typeof(string))) return new object[] { request.Parameters.GetValue(parameterName, true) };
// json // json
if (pit.Equals(typeof(Json))) return new object[] { Json.From(request.Parameters.GetValue(pin, true)) }; if (parameterType.Equals(typeof(Json))) return new object[] { Json.From(request.Parameters.GetValue(parameterName, true)) };
} }
} }
var ps = new object[count]; var values = new object[count];
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
var name = pis[i].Name; if (parameters[i] != null)
var type = pis[i].ParameterType; {
var text = Parameter(request, name); var name = parameters[i].Name;
ps[i] = ReadParameter(text, type); var type = parameters[i].Type;
var text = Parameter(request, name);
values[i] = ReadParameter(text, type);
}
} }
return ps; return values;
} }
static bool ReadParameter(Json json, object entity) static Class<object> ReadParameter(Json json, Type type)
{ {
if (!json) return false; if (json)
if (json.IsObject)
{
var properties = json.GetProperties();
if (properties.Length < 1) return false;
Json.Object(entity, json, true, null, true);
return true;
}
if (json.IsArray)
{ {
var items = json.GetItems(); if (json.IsObject)
if (items.Length < 1) return false; {
Json.Object(entity, json, true, null, true); var properties = json.GetProperties();
return true; if (properties.Length > 0)
{
var entity = Json.Object(type, json, true, null, true);
return new Class<object>(entity);
}
}
if (json.IsArray)
{
var items = json.GetItems();
if (items.Length > 0)
{
var entity = Json.Object(type, json, true, null, true);
return new Class<object>(entity);
}
}
} }
return false; return null;
} }
static object ReadParameter(string text, Type type) static object ReadParameter(string text, Type type)
@ -1004,7 +1043,7 @@ namespace Apewer.Web
} }
var json = Json.NewObject(); var json = Json.NewObject();
json.SetProperty("count", count); json.SetProperty("count", count);
json.SetProperty("list", list); json.SetProperty("applications", list);
return json; return json;
} }
@ -1024,7 +1063,7 @@ namespace Apewer.Web
} }
var json = Json.NewObject(); var json = Json.NewObject();
json.SetProperty("count", count); json.SetProperty("count", count);
json.SetProperty("list", list); json.SetProperty("functions", list);
return json; return json;
} }

65
Apewer/Web/BytesResult.cs

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>表示 API 行为结果,主体为字节数组。</summary>
public class BytesResult : HeadResult
{
#region content
/// <summary>主体。</summary>
public virtual byte[] Bytes { get; set; }
/// <summary>创建结果实例。</summary>
public BytesResult(byte[] bytes, string contentType = "application/octet-stream") : this(200, bytes, contentType) { }
/// <summary>创建结果实例。</summary>
public BytesResult(int status, byte[] bytes, string contentType = "application/octet-stream") : base(status)
{
Headers.Add("Content-Type", contentType);
Bytes = bytes;
}
#endregion
#region execute
/// <summary>写入主体。</summary>
/// <param name="context">API 上下文。</param>
/// <param name="bodyData">主体的内容,字节数应该和 Content-Length 一致。</param>
protected virtual void WriteBody(ApiContext context, byte[] bodyData)
{
if (bodyData != null && bodyData.Length > 0)
{
var stream = context.Provider.ResponseBody();
stream.Write(bodyData, 0, bodyData.Length);
}
}
/// <summary>写入 HTTP 头和主体。</summary>
public override void ExecuteResult(ApiContext context)
{
if (Bytes == null)
{
WriteHead(context, 0);
}
else
{
WriteHead(context, Bytes.Length);
if (Bytes.Length > 0L)
{
var stream = context.Provider.ResponseBody();
stream.Write(Bytes);
}
}
}
#endregion
}
}

44
Apewer/Web/CacheControl.cs

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>缓存控制的值。</summary>
public static class CacheControl
{
/// <summary>不缓存。</summary>
public const string Disabled = "no-cache, no-store, must-revalidate";
/// <summary>1 天的秒数。</summary>
public const int Day = 86400;
/// <summary>30 天的秒数。</summary>
public const int Month = 259200;
/// <summary>365 天的秒数。</summary>
public const int Year = 31536000;
/// <summary>构建缓存指令,此指令允许所有缓存。</summary>
/// <param name="maxAge">浏览器的缓存秒数。</param>
/// <param name="sMaxAge">代理服务器的缓存秒数。</param>
public static string Public(int maxAge, int sMaxAge)
{
if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
if (sMaxAge < 0) throw new ArgumentOutOfRangeException(nameof(sMaxAge));
return $"public, max-age={maxAge}, s-maxage={sMaxAge}, must-revalidate";
}
/// <summary>构建缓存指令,此指令仅允许浏览器缓存。</summary>
/// <param name="maxAge">浏览器的缓存秒数。</param>
public static string Private(int maxAge)
{
if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge));
return $"private, max-age={maxAge}, must-revalidate";
}
}
}

12
Apewer/Web/FromBodyAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class FromBodyAttribute : Attribute { }
}

12
Apewer/Web/FromFormAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class FromFormAttribute : Attribute { }
}

12
Apewer/Web/FromHeaderAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class FromHeaderAttribute : Attribute { }
}

12
Apewer/Web/FromQueryAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class FromQueryAttribute : Attribute { }
}

92
Apewer/Web/HeadResult.cs

@ -0,0 +1,92 @@
using Apewer.Network;
using System;
namespace Apewer.Web
{
/// <summary>表示 API 行为结果,仅包含头。</summary>
public class HeadResult : ActionResult, IActionResult, IHttpActionResult
{
#region content
int _status = 200;
HttpHeaders _headers = new HttpHeaders();
/// <summary>由 RFC 7231 定义的状态码。</summary>
/// <value>1xx (Informational)<br />2xx (Successful)<br />3xx (Redirection)<br />4xx (Client Error)<br />5xx (Server Error)</value>
public virtual int StatusCode
{
get { return _status; }
set { _status = value; }
}
/// <summary>头部。</summary>
public virtual HttpHeaders Headers
{
get { return _headers; }
set { _headers = value ?? new HttpHeaders(); }
}
/// <summary>创建结果实例。</summary>
public HeadResult() : this(200) { }
/// <summary>创建结果实例。</summary>
public HeadResult(int status)
{
StatusCode = status;
}
#endregion
#region execute
/// <summary>写入 HTTP 头。</summary>
/// <param name="context">API 上下文。</param>
/// <param name="contentLength">内容长度。指定为负数时不写入 HTTP 头。</param>
protected virtual void WriteHead(ApiContext context, long contentLength)
{
context.Provider.SetStatus(StatusCode);
const string ContentType = "Content-Type";
const string ContentLength = "Content-Length";
foreach (var header in _headers)
{
if (header.Name.IsEmpty()) continue;
if (header.Value.IsEmpty()) continue;
// Content-Length
if (ContentLength.Equals(header.Name, StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
// Content-Type
if (ContentType.Equals(header.Name, StringComparison.CurrentCultureIgnoreCase))
{
context.Provider.SetContentType(header.Value);
continue;
}
// default
context.Provider.SetHeader(header.Name, header.Value);
}
// Content-Length
if (contentLength >= 0L)
{
context.Provider.SetContentLength(contentLength);
}
}
/// <summary>写入 HTTP 头,其中不包含 Content-Length。</summary>
public override void ExecuteResult(ApiContext context)
{
WriteHead(context, 0L);
}
#endregion
}
}

12
Apewer/Web/HttpConnectAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpConnectAttribute : Attribute { }
}

12
Apewer/Web/HttpDeleteAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpDeleteAttribute : Attribute { }
}

12
Apewer/Web/HttpGetAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpGetAttribute : Attribute { }
}

12
Apewer/Web/HttpHeadAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpHeadAttribute : Attribute { }
}

10
Apewer/Web/HttpListenerProvider.cs

@ -1,4 +1,5 @@
using System; using Apewer.Network;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
@ -7,13 +8,16 @@ namespace Apewer.Web
{ {
/// <summary>用于网站的服务程序。</summary> /// <summary>用于网站的服务程序。</summary>
public class HttpListenerProvider : ApiProvider public class HttpListenerProvider : ApiProvider<HttpListenerContext>
{ {
private HttpListenerContext context; private HttpListenerContext context;
private HttpListenerRequest request; private HttpListenerRequest request;
private HttpListenerResponse response; private HttpListenerResponse response;
/// <summary>HttpContext</summary>
public override HttpListenerContext Context { get => context; }
/// <summary>创建服务程序实例。</summary> /// <summary>创建服务程序实例。</summary>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public HttpListenerProvider(HttpListenerContext context) public HttpListenerProvider(HttpListenerContext context)
@ -59,7 +63,7 @@ namespace Apewer.Web
public override string GetReferrer() => request.UrlReferrer == null ? null : request.UrlReferrer.OriginalString; public override string GetReferrer() => request.UrlReferrer == null ? null : request.UrlReferrer.OriginalString;
/// <summary>获取请求的头。</summary> /// <summary>获取请求的头。</summary>
public override StringPairs GetHeaders() => StringPairs.From(request.Headers); public override HttpHeaders GetHeaders() => new HttpHeaders(request.Headers);
/// <summary>获取请求的内容类型。</summary> /// <summary>获取请求的内容类型。</summary>
public override string GetContentType() => request.ContentType; public override string GetContentType() => request.ContentType;

12
Apewer/Web/HttpOptionsAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpOptionsAttribute : Attribute { }
}

12
Apewer/Web/HttpPatchAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpPatchAttribute : Attribute { }
}

12
Apewer/Web/HttpPostAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpPostAttribute : Attribute { }
}

12
Apewer/Web/HttpPutAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpPutAttribute : Attribute { }
}

12
Apewer/Web/HttpTraceAttribute.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpTraceAttribute : Attribute { }
}

17
Apewer/Web/IActionResult.cs

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>定义如何输出 API 结果。</summary>
public interface IActionResult
{
/// <summary>执行结果。</summary>
void ExecuteResult(ApiContext context);
}
}

17
Apewer/Web/IApiModel.cs

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>定义 API 响应模型。</summary>
public interface IApiModel
{
/// <summary>输出。</summary>
void Output(ApiContext context);
}
}

11
Apewer/Web/IHttpActionResult.cs

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>定义如何输出 API 结果。</summary>
public interface IHttpActionResult : IActionResult { }
}

8
Apewer/Web/MiniConnection.cs

@ -387,12 +387,12 @@ namespace Apewer.Web
} }
// 保持连接。 // 保持连接。
_context.Request.KeepAlive = _context.Request.Http11 && TextUtility.Lower(_context.Request.Headers.GetValue("connection", true)) == "keep-alive"; _context.Request.KeepAlive = _context.Request.Http11 && TextUtility.Lower(_context.Request.Headers.GetValue("Connection")) == "keep-alive";
// 启用压缩。 // 启用压缩。
if (_server.Compression) if (_server.Compression)
{ {
var headerValue = _context.Request.Headers.GetValue("accept-encoding", true); var headerValue = _context.Request.Headers.GetValue("Accept-Encoding");
if (!string.IsNullOrEmpty(headerValue)) if (!string.IsNullOrEmpty(headerValue))
{ {
var split = headerValue.ToLower().Split(','); var split = headerValue.ToLower().Split(',');
@ -408,7 +408,7 @@ namespace Apewer.Web
} }
// URL // URL
var host = _context.Request.Headers.GetValue("host", true); var host = _context.Request.Headers.GetValue("Host");
var port = 0; var port = 0;
var local = LocalEndPoint; var local = LocalEndPoint;
if (local != null) if (local != null)
@ -449,7 +449,7 @@ namespace Apewer.Web
var headers = _context.Request.Headers; var headers = _context.Request.Headers;
var length = -1L; var length = -1L;
var value = headers.GetValue("Content-Length", true); var value = headers.GetValue("Content-Length");
if (!string.IsNullOrEmpty(value)) if (!string.IsNullOrEmpty(value))
{ {
var num = value.Int64(); var num = value.Int64();

11
Apewer/Web/MiniProvider.cs

@ -8,7 +8,7 @@ namespace Apewer.Web
{ {
/// <summary></summary> /// <summary></summary>
public sealed class MiniProvider : ApiProvider public sealed class MiniProvider : ApiProvider<MiniContext>
{ {
MiniConnection connection; MiniConnection connection;
@ -16,6 +16,9 @@ namespace Apewer.Web
MiniRequest request; MiniRequest request;
MiniResponse response; MiniResponse response;
/// <summary>HttpContext</summary>
public override MiniContext Context { get => context; }
/// <summary>创建服务程序实例。</summary> /// <summary>创建服务程序实例。</summary>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public MiniProvider(MiniContext context) public MiniProvider(MiniContext context)
@ -45,16 +48,16 @@ namespace Apewer.Web
public override long GetContentLength() => request.ContentLength; public override long GetContentLength() => request.ContentLength;
/// <summary></summary> /// <summary></summary>
public override string GetContentType() => request.Headers.GetValue("Content-Type", true); public override string GetContentType() => request.Headers.GetValue("Content-Type");
/// <summary></summary> /// <summary></summary>
public override StringPairs GetHeaders() => request.Headers; public override HttpHeaders GetHeaders() => request.Headers;
/// <summary></summary> /// <summary></summary>
public override HttpMethod GetMethod() => NetworkUtility.ParseHttpMethod(request.Method); public override HttpMethod GetMethod() => NetworkUtility.ParseHttpMethod(request.Method);
/// <summary></summary> /// <summary></summary>
public override string GetReferrer() => request.Headers.GetValue("Referrer", true); public override string GetReferrer() => request.Headers.GetValue("Referrer");
/// <summary></summary> /// <summary></summary>
public override Uri GetUrl() => request.Url; public override Uri GetUrl() => request.Url;

9
Apewer/Web/MiniRequest.cs

@ -1,4 +1,5 @@
using System; using Apewer.Network;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
@ -28,10 +29,10 @@ namespace Apewer.Web
#region headers #region headers
StringPairs _headers = new StringPairs(); HttpHeaders _headers = new HttpHeaders();
/// <summary>头部。</summary> /// <summary>头部。</summary>
public StringPairs Headers { get => _headers; } public HttpHeaders Headers { get => _headers; }
/// <summary>统一资源定位。</summary> /// <summary>统一资源定位。</summary>
public Uri Url { get; internal set; } public Uri Url { get; internal set; }
@ -52,7 +53,7 @@ namespace Apewer.Web
public bool Gzip { get; internal set; } public bool Gzip { get; internal set; }
/// <summary>内容长度,单位:字节。</summary> /// <summary>内容长度,单位:字节。</summary>
public long ContentLength { get => _headers.GetValue("Content-Length", true).Int64(); } public long ContentLength { get => _headers.GetValue("Content-Length").Int64(); }
#endregion #endregion

29
Apewer/Web/RouteAttribute.cs

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class RouteAttribute : Attribute
{
string _path;
/// <summary></summary>
public string Path { get => _path; }
/// <summary></summary>
public RouteAttribute() { }
/// <summary></summary>
public RouteAttribute(string path)
{
_path = path;
}
}
}

24
Apewer/Web/RoutePrefixAttribute.cs

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary></summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class RoutePrefixAttribute : Attribute
{
string _path;
/// <summary></summary>
public string Path { get { return _path; } }
/// <summary></summary>
/// <param name="path"></param>
public RoutePrefixAttribute(string path) { _path = path; }
}
}

120
Apewer/Web/StreamResult.cs

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Apewer.Web
{
/// <summary>表示 API 行为结果,主体为流。</summary>
public sealed class StreamResult : HeadResult, IDisposable
{
#region content
/// <summary>主体。</summary>
public Stream Stream { get; set; }
/// <summary>Content-Length 的值,用于限制输出的最大长度。指定为 -1 时不限长度,输出到流的末尾。</summary>
/// <value>Default = -1</value>
public long Length { get; set; }
/// <summary>输出后自动释放流。</summary>
public bool AutoDispose { get; set; }
/// <summary>创建结果实例。</summary>
/// <param name="stream">要输出的流。</param>
/// <param name="contentType">内容类型。</param>
/// <param name="autoDispose">输出后自动释放流。</param>
public StreamResult(Stream stream, string contentType = "application/octet-stream", bool autoDispose = true) : this(200, stream, contentType, -1, autoDispose) { }
/// <summary>创建结果实例。</summary>
/// <param name="status">HTTP 状态码。</param>
/// <param name="stream">要输出的流。</param>
/// <param name="contentType">内容类型。</param>
/// <param name="length">Content-Length 的值。</param>
/// <param name="autoDispose">输出后自动释放流。</param>
public StreamResult(int status, Stream stream, string contentType = "application/octet-stream", long length = -1, bool autoDispose = true) : base(status)
{
Headers.Add("Content-Type", contentType);
Stream = stream;
Length = length;
AutoDispose = autoDispose;
}
#endregion
#region execute
/// <summary>释放系统资源。</summary>
public override void Dispose()
{
if (Stream != null)
{
RuntimeUtility.Dispose(Stream);
Stream = null;
}
}
/// <summary>写入 HTTP 头和主体。</summary>
public override void ExecuteResult(ApiContext context)
{
try
{
if (Stream == null || Length == 0L)
{
WriteHead(context, 0);
}
else
{
WriteHead(context, Length);
var writed = 0L;
var capacity = 4096;
var buffer = new byte[capacity];
var body = context.Provider.ResponseBody();
// 限制长度。
if (Length > 0L)
{
var remains = Length;
while (true)
{
var limit = Math.Min((int)remains, capacity);
var read = Stream.Read(buffer, 0, limit);
if (read < 1) break;
body.Write(buffer, 0, read);
writed += read;
remains -= read;
if (remains < 1L) break;
}
}
// 不限制长度,输出到流的末尾。
else
{
while (true)
{
var read = Stream.Read(buffer, 0, capacity);
if (read < 1) break;
body.Write(buffer, 0, read);
writed += read;
}
}
context.Provider.End();
}
}
finally
{
if (AutoDispose) Dispose();
}
}
#endregion
}
}

20
Apewer/Web/TextResult.cs

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>表示 API 行为结果,主体为文本。</summary>
public class TextResult : BytesResult
{
/// <summary>创建结果实例。</summary>
public TextResult(string text, string contentType = "text/plain") : base(text.Bytes(), contentType) { }
/// <summary>创建结果实例。</summary>
public TextResult(int status, string text, string contentType = "text/plain") : base(status, text.Bytes(), contentType) { }
}
}

8
Apewer/Web/WebsiteProvider.cs

@ -1,5 +1,6 @@
#if NETFX #if NETFX
using Apewer.Network;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -9,13 +10,16 @@ namespace Apewer.Web
{ {
/// <summary>用于网站的服务程序。</summary> /// <summary>用于网站的服务程序。</summary>
public class WebsiteProvider : ApiProvider public class WebsiteProvider : ApiProvider<HttpContext>
{ {
private HttpContext context; private HttpContext context;
private HttpRequest request; private HttpRequest request;
private HttpResponse response; private HttpResponse response;
/// <summary>HttpContext</summary>
public override HttpContext Context { get => context; }
/// <summary>创建服务程序实例。</summary> /// <summary>创建服务程序实例。</summary>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public WebsiteProvider(HttpContext context) public WebsiteProvider(HttpContext context)
@ -65,7 +69,7 @@ namespace Apewer.Web
public override string GetReferrer() => request.UrlReferrer == null ? null : request.UrlReferrer.OriginalString; public override string GetReferrer() => request.UrlReferrer == null ? null : request.UrlReferrer.OriginalString;
/// <summary>获取请求的头。</summary> /// <summary>获取请求的头。</summary>
public override StringPairs GetHeaders() => StringPairs.From(request.Headers); public override HttpHeaders GetHeaders() => new HttpHeaders(request.Headers);
/// <summary>获取请求的内容类型。</summary> /// <summary>获取请求的内容类型。</summary>
public override string GetContentType() => request.ContentType; public override string GetContentType() => request.ContentType;

95
Apewer/Web/_Attributes.cs

@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
#region 路由
/// <summary></summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class RoutePrefixAttribute : Attribute
{
string _path;
/// <summary></summary>
public string Path { get { return _path; } }
/// <summary></summary>
/// <param name="path"></param>
public RoutePrefixAttribute(string path) { _path = path; }
}
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class RouteAttribute : Attribute
{
string _path;
/// <summary></summary>
public string Path { get { return _path; } }
/// <summary></summary>
public RouteAttribute(string path) { _path = path; }
}
#endregion
#region 方法
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpConnectAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpDeleteAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpGetAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpHeadAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpOptionsAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpPatchAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpPostAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpPutAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class HttpTraceAttribute : Attribute { }
#endregion
#region 参数
/// <summary></summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class FromBodyAttribute : Attribute { }
/// <summary></summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class FromUriAttribute : Attribute { }
#endregion
}

35
Apewer/_Extensions.cs

@ -20,10 +20,10 @@ using System.Dynamic;
public static class Extensions public static class Extensions
{ {
/// <summary>是 NULL 值。</summary> /// <summary>是 NULL 值,或是 DBNULL 值。</summary>
public static bool IsNull(this object @this) => @this == null || @this.Equals(DBNull.Value); public static bool IsNull(this object @this) => @this == null || @this.Equals(DBNull.Value);
/// <summary>不是 NULL 值。</summary> /// <summary>不是 NULL 值,且不是 DBNULL 值。</summary>
public static bool NotNull(this object @this) => @this != null && !@this.Equals(DBNull.Value); public static bool NotNull(this object @this) => @this != null && !@this.Equals(DBNull.Value);
/// <summary>是默认值。</summary> /// <summary>是默认值。</summary>
@ -43,16 +43,6 @@ public static class Extensions
/// <summary>调用 Set 方法。</summary> /// <summary>调用 Set 方法。</summary>
public static void Set<T>(this PropertyInfo @this, object instance, T value) => RuntimeUtility.InvokeSet<T>(instance, @this, value); public static void Set<T>(this PropertyInfo @this, object instance, T value) => RuntimeUtility.InvokeSet<T>(instance, @this, value);
/// <summary>调用方法。</summary>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="MethodAccessException"></exception>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="TargetException"></exception>
/// <exception cref="TargetInvocationException"></exception>
/// <exception cref="TargetParameterCountException"></exception>
public static object Invoke(this MethodInfo @this, object instace, params object[] parameters) => RuntimeUtility.InvokeMethod(instace, @this, parameters);
/// <summary>判断静态属性。</summary> /// <summary>判断静态属性。</summary>
public static bool IsStatic(this PropertyInfo @this) => RuntimeUtility.IsStatic(@this); public static bool IsStatic(this PropertyInfo @this) => RuntimeUtility.IsStatic(@this);
@ -239,8 +229,9 @@ public static class Extensions
#region DateTime #region DateTime
/// <summary>获取毫秒时间戳。</summary> /// <summary>获取毫秒时间戳。当指定了 <see cref="ClockUtility.CustomToStamp"/> 时将优先使用自定义的方法。</summary>
public static long Stamp(this DateTime @this, bool byMilliseconds = true) => ClockUtility.Stamp(@this, byMilliseconds); /// <remarks>默认不判断参数的时区,与 <see cref="DateTimeKind.Unspecified"/> 相同。</remarks>
public static long Stamp(this DateTime dt) => ClockUtility.ToStamp(dt);
/// <summary>转换为易于阅读的文本。</summary> /// <summary>转换为易于阅读的文本。</summary>
/// <remarks>格式:1970-01-01 00:00:00.000</remarks> /// <remarks>格式:1970-01-01 00:00:00.000</remarks>
@ -252,9 +243,10 @@ public static class Extensions
/// <summary>当前 DateTime 为闰年。</summary> /// <summary>当前 DateTime 为闰年。</summary>
public static bool LeapYear(this DateTime @this) => ClockUtility.IsLeapYear(@this); public static bool LeapYear(this DateTime @this) => ClockUtility.IsLeapYear(@this);
/// <summary>从毫秒时间戳获取 DateTime 对象。发生异常且不允许异常时将返回 1970-01-01 00:00:00.000。</summary> /// <summary>解析毫秒时间戳,获取 DateTime 对象。当指定了 <see cref="ClockUtility.CustomFromStamp"/> 时将优先使用自定义的方法。</summary>
/// <remarks>默认不判断系统时区,返回的结果是 <see cref="DateTimeKind.Unspecified"/>。</remarks>
/// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception>
public static DateTime DateTime(this long stamp, DateTimeKind kind = DateTimeKind.Unspecified, bool throwException = true) => ClockUtility.FromStamp(stamp, kind, throwException); public static DateTime DateTime(this long stamp) => ClockUtility.FromStamp(stamp);
#region Nullable #region Nullable
@ -265,8 +257,9 @@ public static class Extensions
/// <summary>转换为紧凑的文本。</summary> /// <summary>转换为紧凑的文本。</summary>
public static string Compact(this DateTime? @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds); public static string Compact(this DateTime? @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds);
/// <summary>获取毫秒时间戳。</summary> /// <summary>获取毫秒时间戳。当指定了 <see cref="ClockUtility.CustomToStamp"/> 时将优先使用自定义的方法。</summary>
public static long Stamp(this DateTime? @this, bool byMilliseconds = true) => @this == null ? default : ClockUtility.Stamp(@this.Value, byMilliseconds); /// <remarks>默认不判断参数的时区,与 <see cref="DateTimeKind.Unspecified"/> 相同。</remarks>
public static long Stamp(this DateTime? @this) => @this == null ? default : ClockUtility.ToStamp(@this.Value);
/// <summary>转换为易于阅读的文本。</summary> /// <summary>转换为易于阅读的文本。</summary>
/// <remarks>格式:1970-01-01 00:00:00.000</remarks> /// <remarks>格式:1970-01-01 00:00:00.000</remarks>
@ -276,7 +269,7 @@ public static class Extensions
public static string Compact(this Class<DateTime> @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds); public static string Compact(this Class<DateTime> @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds);
/// <summary>获取毫秒时间戳。</summary> /// <summary>获取毫秒时间戳。</summary>
public static long Stamp(this Class<DateTime> @this, bool byMilliseconds = true) => @this == null ? default : ClockUtility.Stamp(@this.Value, byMilliseconds); public static long Stamp(this Class<DateTime> @this) => @this == null ? default : ClockUtility.ToStamp(@this.Value);
#endregion #endregion
@ -594,13 +587,13 @@ public static class Extensions
public static void Text(this ApiResponse @this, string text, string contentType = "text/plain") => ApiUtility.Model(@this, new ApiTextModel(text, contentType)); public static void Text(this ApiResponse @this, string text, string contentType = "text/plain") => ApiUtility.Model(@this, new ApiTextModel(text, contentType));
/// <summary>输出 Json 文本。</summary> /// <summary>输出 Json 文本。</summary>
public static void Json(this ApiResponse @this, Json json, bool indented = true, bool camel = false) => ApiUtility.Model(@this, new ApiJsonModel(json, camel, indented)); public static void Json(this ApiResponse @this, Json json, bool indented = false, bool camel = true) => ApiUtility.Model(@this, new ApiJsonModel(json, indented, camel));
/// <summary>输出文件。</summary> /// <summary>输出文件。</summary>
public static void File(this ApiResponse @this, string path) => ApiUtility.Model(@this, new ApiFileModel(path)); public static void File(this ApiResponse @this, string path) => ApiUtility.Model(@this, new ApiFileModel(path));
/// <summary>重定向。</summary> /// <summary>重定向。</summary>
public static void Redirect(this ApiResponse @this, string location) => ApiUtility.Model(@this, new ApiRedirectModel() { Location = location }); public static void Redirect(this ApiResponse @this, string location) => ApiUtility.Model(@this, new ApiRedirectModel(location));
#endregion #endregion

7
ChangeLog.md

@ -1,6 +1,13 @@
 
### 最新提交 ### 最新提交
### 6.8.0
- 新特性
- ClockUtility:增加 CustomToStamp 和 CustomFromStamp,支持自定义时间戳的转换方法;
- Json:识别实例类型的 .ctor(Json) 构造函数,由类型自己实现反序列化;
- Web:兼容微软 API,支持 Route 特性,支持返回 ActionResult;
- WindowsUtility:增加读取进程内存的方法。
### 6.7.6 ### 6.7.6
- 新特性 - 新特性
- CollectionUtility:增加数组的 Push 和 Unshift 方法; - CollectionUtility:增加数组的 Push 和 Unshift 方法;

Loading…
Cancel
Save