68 changed files with 3049 additions and 1410 deletions
@ -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 |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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; } |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
@ -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
|
|
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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
|
|
@ -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 |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
@ -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
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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"; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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 { } |
||||
|
|
||||
|
} |
@ -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); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Apewer.Web |
||||
|
{ |
||||
|
|
||||
|
/// <summary>定义如何输出 API 结果。</summary>
|
||||
|
public interface IHttpActionResult : IActionResult { } |
||||
|
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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; } |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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) { } |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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
|
|
||||
|
|
||||
} |
|
Loading…
Reference in new issue