47 changed files with 2695 additions and 2848 deletions
@ -1,528 +0,0 @@ |
|||
#if DEBUG
|
|||
|
|||
/* 2021.11.28 */ |
|||
|
|||
using Apewer; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Data; |
|||
using System.Data.Common; |
|||
using System.Text; |
|||
|
|||
using static Apewer.Source.SourceUtility; |
|||
using System.Data.SqlClient; |
|||
|
|||
#if NETFRAMEWORK
|
|||
using System.Data.Sql; |
|||
#endif
|
|||
|
|||
namespace Apewer.Source |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
[Serializable] |
|||
|
|||
public class SqlClientThin : DbClient, IDbClient |
|||
{ |
|||
|
|||
#region
|
|||
|
|||
string _str = null; |
|||
|
|||
/// <summary>使用连接字符串创建数据库连接实例。</summary>
|
|||
public SqlClientThin(string connectionString, Timeout timeout = null) : base(timeout) |
|||
{ |
|||
_str = connectionString ?? ""; |
|||
} |
|||
|
|||
/// <summary>使用连接凭据创建数据库连接实例。</summary>
|
|||
public SqlClientThin(string address, string store, string user, string pass, Timeout timeout = null) : base(timeout) |
|||
{ |
|||
if (timeout == null) timeout = Timeout.Default; |
|||
|
|||
var a = address ?? ""; |
|||
var s = store ?? ""; |
|||
var u = user ?? ""; |
|||
var p = pass ?? ""; |
|||
var cs = $"data source = {a}; initial catalog = {s}; "; |
|||
if (string.IsNullOrEmpty(u)) cs += "integrated security = sspi; "; |
|||
else |
|||
{ |
|||
cs += $"user id = {u}; "; |
|||
if (!string.IsNullOrEmpty(p)) cs += $"password = {p}; "; |
|||
} |
|||
cs += $"connection timeout = {timeout.Connect}; "; |
|||
|
|||
_str = cs; |
|||
} |
|||
|
|||
/// <summary>为 Ado 创建连接字符串。</summary>
|
|||
protected override string NewConnectionString() => _str; |
|||
|
|||
/// <summary>为 Ado 创建 IDbConnection 对象。</summary>
|
|||
protected override IDbConnection NewConnection() => new SqlConnection(); |
|||
|
|||
/// <summary>为 Ado 创建 IDbCommand 对象。</summary>
|
|||
protected override IDbCommand NewCommand() => new SqlCommand(); |
|||
|
|||
/// <summary>为 Ado 创建 IDataAdapter 对象。</summary>
|
|||
protected override IDataAdapter NewDataAdapter(IDbCommand command) => new SqlDataAdapter((SqlCommand)command); |
|||
|
|||
#endregion
|
|||
|
|||
#region ORM
|
|||
|
|||
/// <summary>查询数据库中的所有表名。</summary>
|
|||
public override string[] TableNames() => TextColumn("select [name] from [sysobjects] where [type] = 'u' order by [name]; "); |
|||
|
|||
/// <summary>查询数据库实例中的所有数据库名。</summary>
|
|||
public override string[] StoreNames() => TextColumn("select [name] from [master]..[sysdatabases] order by [name]; ", new string[] { "master", "model", "msdb", "tempdb" }); |
|||
|
|||
/// <summary>查询表中的所有列名。</summary>
|
|||
public override string[] ColumnNames(string tableName) => TextColumn($"select [name] from [syscolumns] where [id] = object_id('{tableName}'); "); |
|||
|
|||
/// <summary>获取列信息。</summary>
|
|||
public override ColumnInfo[] ColumnsInfo(string tableName) |
|||
{ |
|||
if (tableName.IsEmpty()) throw new ArgumentNullException(nameof(tableName)); |
|||
var sql = $"select name, xtype, length from syscolumns where id = object_id('{tableName}') "; |
|||
using (var query = Query(sql)) |
|||
{ |
|||
var ab = new ArrayBuilder<ColumnInfo>(); |
|||
for (var i = 0; i < query.Rows; i++) |
|||
{ |
|||
var info = new ColumnInfo(); |
|||
info.Name = query.Text(i, "name"); |
|||
info.Type = XType(query.Int32(i, "xtype")); |
|||
info.Length = query.Int32(i, "length"); |
|||
ab.Add(info); |
|||
} |
|||
return ab.Export(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary>
|
|||
public override string Initialize(Type model) |
|||
{ |
|||
var structure = TableStructure.Parse(model); |
|||
if (structure == null) return "无法解析记录模型。"; |
|||
|
|||
// 连接数据库。
|
|||
var connect = Connect(); |
|||
if (connect.NotEmpty()) return connect; |
|||
|
|||
// 检查现存表。
|
|||
var exists = false; |
|||
var tables = TableNames(); |
|||
if (tables.Length > 0) |
|||
{ |
|||
var lower = structure.Name.ToLower(); |
|||
foreach (var table in tables) |
|||
{ |
|||
if (TextUtility.IsBlank(table)) continue; |
|||
if (table.ToLower() == lower) |
|||
{ |
|||
exists = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (exists) |
|||
{ |
|||
// 获取已存在的列名。
|
|||
var columns = ColumnNames(structure.Name); |
|||
if (columns.Length > 0) |
|||
{ |
|||
var lower = new List<string>(); |
|||
foreach (var column in columns) |
|||
{ |
|||
if (TextUtility.IsBlank(column)) continue; |
|||
lower.Add(column.ToLower()); |
|||
} |
|||
columns = lower.ToArray(); |
|||
} |
|||
|
|||
// 增加列。
|
|||
foreach (var column in structure.Columns) |
|||
{ |
|||
// 检查 Independent 特性。
|
|||
if (structure.Independent && column.Independent) continue; |
|||
|
|||
// 去重。
|
|||
var lower = column.Field.ToLower(); |
|||
if (columns.Contains(lower)) continue; |
|||
|
|||
var type = Declaration(column); |
|||
if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); |
|||
|
|||
var sql = TextUtility.Merge("alter table [", structure.Name, "] add ", type, "; "); |
|||
var execute = Execute(sql); |
|||
if (execute.Success == false) return execute.Message; |
|||
} |
|||
return TextUtility.Empty; |
|||
} |
|||
else |
|||
{ |
|||
var sqlcolumns = new List<string>(); |
|||
foreach (var column in structure.Columns) |
|||
{ |
|||
// 检查 Independent 特性。
|
|||
if (structure.Independent && column.Independent) continue; |
|||
|
|||
var type = Declaration(column); |
|||
if (!column.Independent && column.Property.Name == "Key") type = type + " primary key"; |
|||
|
|||
if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); |
|||
sqlcolumns.Add(type); |
|||
} |
|||
var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); |
|||
var execute = Execute(sql); |
|||
if (execute.Success) return TextUtility.Empty; |
|||
return execute.Message; |
|||
} |
|||
} |
|||
|
|||
/// <summary>插入记录。返回错误信息。</summary>
|
|||
public override string Insert(object record, string table = null) |
|||
{ |
|||
if (record == null) return "参数无效。"; |
|||
FixProperties(record); |
|||
|
|||
var structure = TableStructure.Parse(record.GetType()); |
|||
if (structure == null) return "无法解析记录模型。"; |
|||
if (string.IsNullOrEmpty(table)) table = structure.Name; |
|||
if (string.IsNullOrEmpty(table)) return "表名称无效。"; |
|||
|
|||
var ps = structure.CreateParameters(record, Parameter, null); |
|||
var psc = ps.Length; |
|||
if (psc < 1) return "数据模型不包含字段。"; |
|||
|
|||
var names = new List<string>(psc); |
|||
var values = new List<string>(psc); |
|||
foreach (var column in ps) |
|||
{ |
|||
//names.Add(TextGenerator.Merge("[", column, "]"));
|
|||
names.Add(TextUtility.Merge(column)); |
|||
values.Add("@" + column); |
|||
} |
|||
var sb = new StringBuilder(); |
|||
sb.Append("insert into [", table, "](", string.Join(", ", names.ToArray()), ") "); |
|||
sb.Append("values(", string.Join(", ", values.ToArray()), "); "); |
|||
var sql = sb.ToString(); |
|||
|
|||
var execute = Execute(sql, ps); |
|||
if (execute.Success) return TextUtility.Empty; |
|||
return execute.Message; |
|||
} |
|||
|
|||
/// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary>
|
|||
/// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks>
|
|||
public override string Update(IRecord record, string table = null) |
|||
{ |
|||
if (record == null) return "参数无效。"; |
|||
FixProperties(record); |
|||
SetUpdated(record); |
|||
|
|||
var structure = TableStructure.Parse(record.GetType()); |
|||
if (structure == null) return "无法解析记录模型。"; |
|||
if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; |
|||
if (string.IsNullOrEmpty(table)) table = structure.Name; |
|||
if (string.IsNullOrEmpty(table)) return "表名称无效。"; |
|||
|
|||
var ps = structure.CreateParameters(record, Parameter, null); |
|||
var psc = ps.Length; |
|||
if (psc < 1) return "数据模型不包含字段。"; |
|||
|
|||
var items = new List<string>(); |
|||
foreach (var p in ps) |
|||
{ |
|||
var pn = p.ParameterName; |
|||
items.Add(TextUtility.Merge("[", pn, "] = @", pn)); |
|||
} |
|||
var key = record.Key.SafeKey(); |
|||
var sql = TextUtility.Merge("update [", table, "] set ", string.Join(", ", items.ToArray()), " where [_key]='", key, "'; "); |
|||
|
|||
var execute = Execute(sql, ps); |
|||
if (execute.Success) return TextUtility.Empty; |
|||
return execute.Message; |
|||
} |
|||
|
|||
/// <summary>获取记录。</summary>
|
|||
public override Result<object[]> Query(Type model, long flag = 0) => SourceUtility.Query(this, model, (tn) => |
|||
{ |
|||
if (flag == 0) return $"select * from [{tn}]; "; |
|||
return $"select * from [{tn}] where _flag={flag}; "; |
|||
}); |
|||
|
|||
/// <summary>获取记录。</summary>
|
|||
public override Result<T[]> Query<T>(long flag = 0) => SourceUtility.Query<T>(this, (tn) => |
|||
{ |
|||
if (flag == 0) return $"select * from [{tn}]; "; |
|||
return $"select * from [{tn}] where _flag={flag}; "; |
|||
}); |
|||
|
|||
/// <summary>获取具有指定 Key 的记录。</summary>
|
|||
public override Result<object> Get(Type model, string key, long flag = 0) => SourceUtility.Get(this, model, key, (tn, sk) => |
|||
{ |
|||
if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; "; |
|||
return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; |
|||
}); |
|||
|
|||
/// <summary>获取具有指定 Key 的记录。</summary>
|
|||
public override Result<T> Get<T>(string key, long flag = 0) => SourceUtility.Get<T>(this, key, (tn, sk) => |
|||
{ |
|||
if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; "; |
|||
return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; |
|||
}); |
|||
|
|||
/// <summary>查询有效的 Key 值。</summary>
|
|||
public override Result<string[]> Keys(Type model, long flag = 0) => SourceUtility.Keys(this, model, (tn) => |
|||
{ |
|||
if (flag == 0) return $"select _key from [{tn}]; "; |
|||
return $"select _key from [{tn}] where _flag={flag}; "; |
|||
}); |
|||
|
|||
/// <summary>查询有效的 Key 值。</summary>
|
|||
public override Result<string[]> Keys<T>(long flag = 0) => Keys(typeof(T), flag); |
|||
|
|||
#endregion
|
|||
|
|||
#region public static
|
|||
|
|||
#if NET20 || NET40
|
|||
|
|||
/// <summary>枚举本地网络中服务器的名称。</summary>
|
|||
public static SqlServerSource[] EnumerateServer() |
|||
{ |
|||
var list = new List<SqlServerSource>(); |
|||
|
|||
// 表中列名:ServerName、InstanceName、IsClustered、Version。
|
|||
using (var query = new Query(SqlDataSourceEnumerator.Instance.GetDataSources())) |
|||
{ |
|||
for (int i = 0; i < query.Rows; i++) |
|||
{ |
|||
var item = new SqlServerSource(); |
|||
item.ServerName = query.Text(i, "ServerName"); |
|||
list.Add(item); |
|||
} |
|||
} |
|||
return list.ToArray(); |
|||
} |
|||
|
|||
#endif
|
|||
|
|||
/// <summary>指定的连接凭据是否符合连接要求,默认指定 master 数据库。</summary>
|
|||
public static bool Proven(string address, string user, string pass) => Proven(address, "master", user, pass); |
|||
|
|||
/// <summary>指定的连接凭据是否符合连接要求。</summary>
|
|||
public static bool Proven(string address, string store, string user, string pass) |
|||
{ |
|||
var a = string.IsNullOrEmpty(address); |
|||
var s = string.IsNullOrEmpty(store); |
|||
var u = string.IsNullOrEmpty(user); |
|||
var p = string.IsNullOrEmpty(pass); |
|||
if (a) return false; |
|||
if (s) return false; |
|||
if (u && !p) return false; |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
/// <exception cref="ArgumentNullException"></exception>
|
|||
/// <exception cref="InvalidOperationException"></exception>
|
|||
static SqlParameter Parameter(Parameter parameter) |
|||
{ |
|||
if (parameter == null) throw new InvalidOperationException("参数无效。"); |
|||
return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
public static SqlParameter Parameter(string name, ColumnType type, int size, object value) |
|||
{ |
|||
var vname = TextUtility.Trim(name); |
|||
if (TextUtility.IsBlank(vname)) return null; |
|||
|
|||
var vtype = SqlDbType.BigInt; |
|||
switch (type) |
|||
{ |
|||
case ColumnType.Bytes: |
|||
vtype = SqlDbType.Image; |
|||
break; |
|||
case ColumnType.Integer: |
|||
vtype = SqlDbType.BigInt; |
|||
break; |
|||
case ColumnType.Float: |
|||
vtype = SqlDbType.Float; |
|||
break; |
|||
case ColumnType.DateTime: |
|||
vtype = SqlDbType.DateTime; |
|||
break; |
|||
case ColumnType.VarChar: |
|||
case ColumnType.VarChar191: |
|||
case ColumnType.VarCharMax: |
|||
vtype = SqlDbType.VarChar; |
|||
break; |
|||
case ColumnType.NVarChar: |
|||
case ColumnType.NVarChar191: |
|||
case ColumnType.NVarCharMax: |
|||
vtype = SqlDbType.NVarChar; |
|||
break; |
|||
case ColumnType.Text: |
|||
vtype = SqlDbType.Text; |
|||
break; |
|||
case ColumnType.NText: |
|||
vtype = SqlDbType.NText; |
|||
break; |
|||
default: |
|||
throw new InvalidOperationException(TextUtility.Merge("类型 ", type.ToString(), " 不受支持。")); |
|||
} |
|||
|
|||
var vsize = size; |
|||
switch (type) |
|||
{ |
|||
case ColumnType.VarChar: |
|||
vsize = NumberUtility.Restrict(vsize, 0, 8000); |
|||
break; |
|||
case ColumnType.NVarChar: |
|||
vsize = NumberUtility.Restrict(vsize, 0, 4000); |
|||
break; |
|||
case ColumnType.VarChar191: |
|||
case ColumnType.NVarChar191: |
|||
vsize = NumberUtility.Restrict(vsize, 0, 191); |
|||
break; |
|||
default: |
|||
vsize = 0; |
|||
break; |
|||
} |
|||
|
|||
var vvalue = value; |
|||
if (vvalue is string && vvalue != null && vsize > 0) |
|||
{ |
|||
vvalue = TextUtility.Left((string)vvalue, vsize); |
|||
} |
|||
|
|||
var parameter = new SqlParameter(); |
|||
parameter.ParameterName = vname; |
|||
parameter.SqlDbType = vtype; |
|||
parameter.Value = vvalue; |
|||
if (vsize > 0) parameter.Size = vsize; |
|||
return parameter; |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
public static SqlParameter Parameter(string name, SqlDbType type, int size, object value) |
|||
{ |
|||
if (value is string && value != null && size > 0) |
|||
{ |
|||
value = TextUtility.Left((string)value, (int)size); |
|||
} |
|||
|
|||
var p = new SqlParameter(); |
|||
p.ParameterName = name ?? ""; |
|||
p.SqlDbType = type; |
|||
p.Size = size; |
|||
p.Value = value; |
|||
return p; |
|||
} |
|||
|
|||
/// <summary>创建参数。</summary>
|
|||
public static SqlParameter Parameter(string name, SqlDbType type, object value) |
|||
{ |
|||
var p = new SqlParameter(); |
|||
p.ParameterName = name ?? ""; |
|||
p.SqlDbType = type; |
|||
p.Value = value; |
|||
return p; |
|||
} |
|||
|
|||
static string Declaration(ColumnAttribute column) |
|||
{ |
|||
var type = TextUtility.Empty; |
|||
var vcolumn = column; |
|||
var length = Math.Max(0, vcolumn.Length); |
|||
switch (vcolumn.Type) |
|||
{ |
|||
case ColumnType.Integer: |
|||
type = "bigint"; |
|||
break; |
|||
case ColumnType.Float: |
|||
type = "float"; |
|||
break; |
|||
case ColumnType.Bytes: |
|||
type = "image"; |
|||
break; |
|||
case ColumnType.DateTime: |
|||
type = "datetime"; |
|||
break; |
|||
case ColumnType.VarChar: |
|||
type = TextUtility.Merge("varchar(", Math.Min(8000, length).ToString(), ")"); |
|||
break; |
|||
case ColumnType.VarChar191: |
|||
type = TextUtility.Merge("varchar(191)"); |
|||
break; |
|||
case ColumnType.VarCharMax: |
|||
type = TextUtility.Merge("varchar(max)"); |
|||
break; |
|||
case ColumnType.Text: |
|||
type = TextUtility.Merge("text"); |
|||
break; |
|||
case ColumnType.NVarChar: |
|||
type = TextUtility.Merge("nvarchar(", Math.Min(4000, length).ToString(), ")"); |
|||
break; |
|||
case ColumnType.NVarChar191: |
|||
type = TextUtility.Merge("nvarchar(191)"); |
|||
break; |
|||
case ColumnType.NVarCharMax: |
|||
type = TextUtility.Merge("nvarchar(max)"); |
|||
break; |
|||
case ColumnType.NText: |
|||
type = TextUtility.Merge("ntext"); |
|||
break; |
|||
default: |
|||
return TextUtility.Empty; |
|||
} |
|||
return TextUtility.Merge("[", vcolumn.Field, "] ", type); |
|||
} |
|||
|
|||
static string XType(int xtype) |
|||
{ |
|||
switch (xtype) |
|||
{ |
|||
case 34: return "image"; |
|||
case 35: return "text"; |
|||
case 36: return "uniqueidentifier"; |
|||
case 48: return "tinyint"; |
|||
case 52: return "smallint"; |
|||
case 56: return "int"; |
|||
case 58: return "smalldatetime"; |
|||
case 59: return "real"; |
|||
case 60: return "money"; |
|||
case 61: return "datetime"; |
|||
case 62: return "float"; |
|||
case 98: return "sql_variant"; |
|||
case 99: return "ntext"; |
|||
case 104: return "bit"; |
|||
case 106: return "decimal"; |
|||
case 108: return "numeric"; |
|||
case 122: return "smallmoney"; |
|||
case 127: return "bigint"; |
|||
case 165: return "varbinary"; |
|||
case 167: return "varchar"; |
|||
case 173: return "binary"; |
|||
case 175: return "char"; |
|||
case 189: return "timestamp"; |
|||
case 231: return "nvarchar"; |
|||
case 239: return "nchar"; |
|||
case 241: return "xml"; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
#endif
|
@ -0,0 +1,67 @@ |
|||
using Apewer.Web; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public abstract class ApiController |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public ApiRequest Request { get; internal set; } |
|||
|
|||
/// <summary></summary>
|
|||
public ApiResponse Response { get; internal set; } |
|||
|
|||
/// <summary></summary>
|
|||
protected virtual IHttpActionResult Bytes(byte[] bytes, string contentType = "application/octet-stream", string attachmentName = null) |
|||
{ |
|||
var har = new HttpActionResult(); |
|||
har.Bytes = bytes; |
|||
har.ContentType = contentType; |
|||
har.Attachment = attachmentName; |
|||
return har; |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
protected virtual IHttpActionResult Text(string text, string contentType = "text/plain") |
|||
{ |
|||
var har = new HttpActionResult(); |
|||
har.Bytes = text.Bytes(); |
|||
har.ContentType = contentType; |
|||
return har; |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
protected virtual IHttpActionResult Json<T>(T content) |
|||
{ |
|||
var json = Apewer.Json.From(content, false, -1, true); |
|||
var text = json == null ? "" : json.ToString(); |
|||
return Bytes(text.Bytes(), "application/json"); |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
protected static string MapPath(string relativePath) |
|||
{ |
|||
var root = RuntimeUtility.ApplicationPath; |
|||
var path = root; |
|||
if (relativePath.NotEmpty()) |
|||
{ |
|||
var split = relativePath.Split('/'); |
|||
foreach (var seg in split) |
|||
{ |
|||
if (seg.IsEmpty()) continue; |
|||
if (seg == "~") path = root; |
|||
path = Path.Combine(path, seg); |
|||
} |
|||
} |
|||
return path; |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,83 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Class, 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, 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; } |
|||
|
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
public class FromBodyAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
public class FromUriAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpConnectAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpDeleteAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpGetAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpHeadAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpOptionsAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpPatchAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpPostAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpPutAttribute : Attribute { } |
|||
|
|||
/// <summary></summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
public sealed class HttpTraceAttribute : Attribute { } |
|||
|
|||
} |
@ -0,0 +1,211 @@ |
|||
using Apewer.Web; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reflection; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
|
|||
using static Apewer.NumberUtility; |
|||
using static Apewer.Internals.ApiHelper; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary>桥接 ASP.NET 的控制器。</summary>
|
|||
public class BridgeController : Web.ApiController |
|||
{ |
|||
|
|||
#region static routes
|
|||
|
|||
static RouteItem[] _routes = null; |
|||
|
|||
/// <summary>导出所有路由项。</summary>
|
|||
public static Json ExportRoutes() |
|||
{ |
|||
var array = Json.NewArray(); |
|||
if (_routes != null) |
|||
{ |
|||
foreach (var route in _routes) |
|||
{ |
|||
var item = route.ToJson(); |
|||
array.AddItem(item); |
|||
} |
|||
} |
|||
return array; |
|||
} |
|||
|
|||
/// <summary>初始化路由。</summary>
|
|||
/// <param name="assemblies">包含控制器的程序集。</param>
|
|||
/// <param name="withVoid">包含返回 System.Void 的方法。</param>
|
|||
public static void Initialize(IEnumerable<Assembly> assemblies, bool withVoid = false) => _routes = RouteItem.Parse(assemblies, withVoid); |
|||
|
|||
/// <summary>初始化路由,不包含返回 System.Void 的方法。</summary>
|
|||
/// <param name="assemblies">包含控制器的程序集。</param>
|
|||
public static void Initialize(params Assembly[] assemblies) => _routes = RouteItem.Parse(assemblies, false); |
|||
|
|||
#endregion
|
|||
|
|||
#region events
|
|||
|
|||
/// <summary>发生异常时候的处理程序。</summary>
|
|||
public static OnException OnException { get; set; } |
|||
|
|||
/// <summary>输出异常。</summary>
|
|||
/// <exception cref="ArgumentNullException"></exception>
|
|||
public static void Output(ApiRequest request, ApiResponse response, MethodInfo method, Exception exception) |
|||
{ |
|||
if (request == null) throw new ArgumentNullException(nameof(request)); |
|||
if (response == null) throw new ArgumentNullException(nameof(response)); |
|||
if (method == null) throw new ArgumentNullException(nameof(method)); |
|||
if (exception == null) throw new ArgumentNullException(nameof(exception)); |
|||
|
|||
var sb = new StringBuilder(); |
|||
if (request.IP.NotEmpty()) |
|||
{ |
|||
sb.Append(request.IP); |
|||
sb.Append(" "); |
|||
} |
|||
sb.Append(request.Method.ToString()); |
|||
if (request.Url != null) |
|||
{ |
|||
sb.Append(" "); |
|||
sb.Append(request.Url.OriginalString); |
|||
} |
|||
if (method != null) |
|||
{ |
|||
sb.Append("\r\n"); |
|||
sb.Append(method.DeclaringType.FullName); |
|||
sb.Append("."); |
|||
sb.Append(method.Name); |
|||
} |
|||
sb.Append("\r\n\r\n"); |
|||
sb.Append(new ApiExceptionModel(exception).ToString()); |
|||
|
|||
var model = new ApiTextModel(sb.ToString()); |
|||
model.Status = 500; |
|||
response.Model = model; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region instance
|
|||
|
|||
/// <summary></summary>
|
|||
public BridgeController() : base((c) => ((BridgeController)c).Execute(), (c) => ((BridgeController)c).Default()) { } |
|||
|
|||
bool Execute() |
|||
{ |
|||
if (_routes == null) Initialize(); |
|||
|
|||
var routes = _routes; |
|||
var route = RouteItem.Match(routes, Request.Url.AbsolutePath, Request.Method); |
|||
try |
|||
{ |
|||
Execute(route); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.Write("Route Error", ex.InnerException.GetType().FullName, ex.InnerException.Message); |
|||
var action = OnException; |
|||
if (action != null) action.Invoke(Request, Response, route.Method, ex.InnerException); |
|||
else Response.Model = new ApiStatusModel(500); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
void Default() { } |
|||
|
|||
void Execute(RouteItem route) |
|||
{ |
|||
if (route == null) |
|||
{ |
|||
Response.Model = new ApiStatusModel(404); |
|||
return; |
|||
} |
|||
|
|||
// 检查 HTTP 方法
|
|||
switch (Request.Method) |
|||
{ |
|||
case Network.HttpMethod.GET: |
|||
if (!route.Get) |
|||
{ |
|||
Response.Model = new ApiStatusModel(405); |
|||
return; |
|||
} |
|||
break; |
|||
case Network.HttpMethod.POST: |
|||
if (!route.Post) |
|||
{ |
|||
Response.Model = new ApiStatusModel(405); |
|||
return; |
|||
} |
|||
break; |
|||
default: |
|||
Response.Model = new ApiStatusModel(405); |
|||
return; |
|||
} |
|||
|
|||
// 准备参数。
|
|||
var ps = ReadParameters(Request, route.Parameters); |
|||
|
|||
// 准备控制器。
|
|||
var c = Activator.CreateInstance(route.Controller) as ApiController; |
|||
c.Request = Request; |
|||
c.Response = Response; |
|||
|
|||
// 调用 API 方法。
|
|||
var r = route.Method.Invoke(c, ps); |
|||
if (r == null) return; |
|||
|
|||
// 识别返回类型。
|
|||
Response.Model = Model(r); |
|||
} |
|||
|
|||
static ApiModel Model(object value) |
|||
{ |
|||
if (value == null) return null; |
|||
|
|||
if (value is ApiModel model) return model; |
|||
if (value is string text) return new ApiTextModel(text); |
|||
if (value is byte[] bytes) return new ApiBytesModel(bytes); |
|||
if (value is Json json) return new ApiJsonModel(json); |
|||
if (value is HttpActionResult har) return ToModel(har); |
|||
if (value is HttpResponseMessage hrm) return ToModel(hrm); |
|||
|
|||
return new ApiTextModel(value.ToString()); |
|||
} |
|||
|
|||
static ApiModel ToModel(HttpActionResult har) |
|||
{ |
|||
if (har == null) return new ApiStatusModel(204); |
|||
var model = new ApiBytesModel(); |
|||
model.Status = har.Status; |
|||
model.Bytes = har.Bytes; |
|||
model.ContentType = har.ContentType; |
|||
model.Attachment = har.Attachment; |
|||
return model; |
|||
} |
|||
|
|||
static ApiModel ToModel(HttpResponseMessage hrm) |
|||
{ |
|||
if (hrm == null) return new ApiStatusModel(204); |
|||
if (hrm.Content == null || hrm.Content.Stream == null) return new ApiStatusModel(204); |
|||
|
|||
var model = new ApiStreamModel(hrm.Content.Stream); |
|||
model.Status = (int)hrm.StatusCode; |
|||
if (hrm.Content.Headers != null) |
|||
{ |
|||
model.Headers = new StringPairs(); |
|||
if (hrm.Content.Headers.ContentType != null) model.ContentType = hrm.Content.Headers.ContentType.MediaType; |
|||
if (hrm.Content.Headers.ContentDisposition != null) model.Attachment = hrm.Content.Headers.ContentDisposition.FileName; |
|||
model.Headers.Add("Content-Length", hrm.Content.Headers.ContentLength.ToString()); |
|||
} |
|||
model.Stream = hrm.Content.Stream; |
|||
return model; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public class ContentDispositionHeaderValue |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public string FileName { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public ContentDispositionHeaderValue(string dispositionType) => FileName = dispositionType; |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,39 @@ |
|||
using Apewer.Web; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public interface IHttpActionResult { } |
|||
|
|||
/// <summary></summary>
|
|||
public class HttpActionResult : IHttpActionResult |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public int Status { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public string ContentType { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public string Attachment { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public byte[] Bytes { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public StringPairs Cookies { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public HttpActionResult() |
|||
{ |
|||
Cookies = new StringPairs(); |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public abstract class HttpContent : IDisposable |
|||
{ |
|||
|
|||
HttpContentHeaders _headers = new HttpContentHeaders(); |
|||
|
|||
/// <summary></summary>
|
|||
public Stream Stream { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public HttpContentHeaders Headers { get => _headers; } |
|||
|
|||
/// <summary></summary>
|
|||
public void Dispose() => RuntimeUtility.Dispose(Stream); |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public class HttpContentHeaders |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public ContentDispositionHeaderValue ContentDisposition { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public MediaTypeHeaderValue ContentType { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public long ContentLength { get; set; } |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,49 @@ |
|||
using Apewer.Web; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public class HttpResponseMessage : IDisposable |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public HttpContent Content { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public HttpStatusCode StatusCode { get; set; } |
|||
|
|||
private string ReasonPhrase { get; set; } |
|||
|
|||
private bool IsSuccessStatusCode { get { var code = (int)StatusCode; return code >= 200 && code <= 299; } } |
|||
|
|||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|||
public HttpResponseMessage(HttpStatusCode statusCode = HttpStatusCode.OK) |
|||
{ |
|||
var code = (int)StatusCode; |
|||
if (code < 0 || code > 999) throw new ArgumentOutOfRangeException("statusCode"); |
|||
StatusCode = statusCode; |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public override string ToString() => ""; |
|||
|
|||
private bool ContainsNewLineCharacter(string value) |
|||
{ |
|||
foreach (char c in value) |
|||
{ |
|||
if (c == '\r' || c == '\n') return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public void Dispose() { } |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public class JsonResult : HttpActionResult |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public JsonResult(object content) |
|||
{ |
|||
var json = Json.From(content); |
|||
var text = json == null ? "" : json.ToString(); |
|||
Bytes = text.Bytes(); |
|||
ContentType = "application/json"; |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public class MediaTypeHeaderValue |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public string MediaType { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public MediaTypeHeaderValue(string mediaType) => MediaType = mediaType; |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,196 @@ |
|||
using Apewer.Network; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
[Serializable] |
|||
internal class RouteItem : IToJson |
|||
{ |
|||
|
|||
public string Path; |
|||
|
|||
public string Lower; |
|||
|
|||
public Type Controller; |
|||
|
|||
public MethodInfo Method; |
|||
|
|||
public string MethodName; |
|||
|
|||
public bool Connect; |
|||
public bool Delete; |
|||
public bool Get; |
|||
public bool Head; |
|||
public bool Options; |
|||
public bool Patch; |
|||
public bool Post; |
|||
public bool Put; |
|||
public bool Trace; |
|||
|
|||
public ParameterInfo[] Parameters; |
|||
|
|||
public Type Return; |
|||
|
|||
public Json ToJson() |
|||
{ |
|||
var ps = Json.NewObject(); |
|||
foreach (var parameter in Parameters) ps.SetProperty(parameter.Name, parameter.ParameterType.FullName); |
|||
|
|||
var methods = Json.NewArray(); |
|||
if (Connect) methods.AddItem("connect"); |
|||
if (Delete) methods.AddItem("delete"); |
|||
if (Get) methods.AddItem("get"); |
|||
if (Head) methods.AddItem("head"); |
|||
if (Options) methods.AddItem("options"); |
|||
if (Patch) methods.AddItem("patch"); |
|||
if (Post) methods.AddItem("post"); |
|||
if (Put) methods.AddItem("put"); |
|||
if (Trace) methods.AddItem("trace"); |
|||
|
|||
var json = Json.NewObject(); |
|||
json.SetProperty("path", Path); |
|||
json.SetProperty("controller", Controller.FullName); |
|||
json.SetProperty("function", Method.Name); |
|||
json.SetProperty("return", Return.FullName); |
|||
json.SetProperty("parameters", ps); |
|||
json.SetProperty("methods", methods); |
|||
|
|||
return json; |
|||
} |
|||
|
|||
#region Enumerate
|
|||
|
|||
internal static RouteItem[] Parse(IEnumerable<Assembly> assemblies, bool withVoid) |
|||
{ |
|||
if (assemblies.IsEmpty()) assemblies = AppDomain.CurrentDomain.GetAssemblies(); |
|||
var baseType = typeof(ApiController); |
|||
var items = new ArrayBuilder<RouteItem>(); |
|||
foreach (var assembly in assemblies) |
|||
{ |
|||
var types = assembly.GetExportedTypes(); |
|||
foreach (var type in types) |
|||
{ |
|||
if (type.IsNotPublic) continue; |
|||
if (!type.IsClass) continue; |
|||
if (!RuntimeUtility.IsInherits(type, baseType)) continue; |
|||
|
|||
if (type.Name == "AccountController") |
|||
{ |
|||
} |
|||
|
|||
var pa = RuntimeUtility.GetAttribute<RoutePrefixAttribute>(type, false); |
|||
var prefix = (pa == null || pa.Path.IsEmpty()) ? null : pa.Path.Split('/'); |
|||
|
|||
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); |
|||
foreach (var method in methods) |
|||
{ |
|||
if (method.IsConstructor) continue; |
|||
if (method.IsGenericMethod) continue; |
|||
if (!withVoid) |
|||
{ |
|||
var returnType = method.ReturnType; |
|||
if (returnType == null || returnType.Equals(typeof(void))) continue; |
|||
} |
|||
|
|||
var route = RuntimeUtility.GetAttribute<RouteAttribute>(method); |
|||
if (route == null || route.Path.IsEmpty()) continue; |
|||
|
|||
var path = Concat(prefix, route.Path.Split('/')); |
|||
|
|||
var item = new RouteItem(); |
|||
item.Controller = type; |
|||
item.Method = method; |
|||
item.MethodName = method.Name; |
|||
item.Parameters = method.GetParameters(); |
|||
item.Return = method.ReturnType; |
|||
item.Path = path; |
|||
item.Lower = path.Lower(); |
|||
|
|||
item.Get = RuntimeUtility.Contains<HttpGetAttribute>(method); |
|||
item.Post = RuntimeUtility.Contains<HttpPostAttribute>(method); |
|||
|
|||
item.Connect = RuntimeUtility.Contains<HttpConnectAttribute>(method); |
|||
item.Delete = RuntimeUtility.Contains<HttpDeleteAttribute>(method); |
|||
item.Head = RuntimeUtility.Contains<HttpHeadAttribute>(method); |
|||
item.Options = RuntimeUtility.Contains<HttpOptionsAttribute>(method); |
|||
item.Patch = RuntimeUtility.Contains<HttpPatchAttribute>(method); |
|||
item.Put = RuntimeUtility.Contains<HttpPutAttribute>(method); |
|||
item.Trace = RuntimeUtility.Contains<HttpTraceAttribute>(method); |
|||
|
|||
items.Add(item); |
|||
} |
|||
} |
|||
} |
|||
return items.Export(); |
|||
} |
|||
|
|||
internal static RouteItem Match(RouteItem[] routes, string path, HttpMethod method) |
|||
{ |
|||
if (routes == null) return null; |
|||
if (path.IsEmpty()) path = "/"; |
|||
|
|||
var length = routes.Length; |
|||
for (var i = 0; i < length; i++) |
|||
{ |
|||
var route = routes[i]; |
|||
if (route == null) continue; |
|||
if (route.Path != path) continue; |
|||
|
|||
if (method == HttpMethod.GET && route.Get) return route; |
|||
if (method == HttpMethod.POST && route.Post) return route; |
|||
if (method == HttpMethod.CONNECT && route.Connect) return route; |
|||
if (method == HttpMethod.DELETE && route.Delete) return route; |
|||
if (method == HttpMethod.HEAD && route.Head) return route; |
|||
if (method == HttpMethod.OPTIONS && route.Options) return route; |
|||
if (method == HttpMethod.PATCH && route.Patch) return route; |
|||
if (method == HttpMethod.PUT && route.Put) return route; |
|||
if (method == HttpMethod.TRACE && route.Trace) return route; |
|||
} |
|||
|
|||
var lower = path.Lower(); |
|||
for (var i = 0; i < length; i++) |
|||
{ |
|||
var route = routes[i]; |
|||
if (route == null) continue; |
|||
if (route.Lower != lower) continue; |
|||
|
|||
if (method == HttpMethod.GET && route.Get) return route; |
|||
if (method == HttpMethod.POST && route.Post) return route; |
|||
if (method == HttpMethod.CONNECT && route.Connect) return route; |
|||
if (method == HttpMethod.DELETE && route.Delete) return route; |
|||
if (method == HttpMethod.HEAD && route.Head) return route; |
|||
if (method == HttpMethod.OPTIONS && route.Options) return route; |
|||
if (method == HttpMethod.PATCH && route.Patch) return route; |
|||
if (method == HttpMethod.PUT && route.Put) return route; |
|||
if (method == HttpMethod.TRACE && route.Trace) return route; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
static string Concat(string[] prefix, string[] path) |
|||
{ |
|||
var all = new List<string>(16); |
|||
|
|||
if (prefix != null) all.AddRange(prefix); |
|||
if (path != null) all.AddRange(path); |
|||
|
|||
var segs = new List<string>(all.Count); |
|||
foreach (var seg in all) |
|||
{ |
|||
if (seg.IsEmpty()) continue; |
|||
segs.Add(seg); |
|||
} |
|||
if (segs.Count < 1) return "/"; |
|||
return "/" + TextUtility.Join("/", segs.ToArray()); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public class StreamContent : HttpContent |
|||
{ |
|||
|
|||
/// <summary></summary>
|
|||
public StreamContent(Stream stream) => Stream = stream; |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,12 @@ |
|||
using Apewer.Web; |
|||
using System; |
|||
using System.Reflection; |
|||
|
|||
namespace Apewer.AspNetBridge |
|||
{ |
|||
|
|||
/// <summary>输出异常。</summary>
|
|||
/// <exception cref="ArgumentNullException"></exception>
|
|||
public delegate void OnException(ApiRequest request, ApiResponse response, MethodInfo method, Exception exception); |
|||
|
|||
} |
@ -1,130 +1,141 @@ |
|||
using System; |
|||
|
|||
/// <summary>装箱类。</summary>
|
|||
public sealed class Class<T> : IComparable, IComparable<T>, IComparable<Class<T>> |
|||
namespace Apewer |
|||
{ |
|||
|
|||
private bool _hashcode = false; |
|||
private bool _equals = false; |
|||
/// <summary>装箱类。</summary>
|
|||
public sealed class Class<T> : IComparable, IComparable<T>, IComparable<Class<T>> |
|||
{ |
|||
|
|||
/// <summary>装箱对象。</summary>
|
|||
public T Value { get; set; } |
|||
private bool _hashcode = false; |
|||
private bool _equals = false; |
|||
|
|||
/// <summary></summary>
|
|||
public bool IsNull { get { return Value == null; } } |
|||
/// <summary>装箱对象。</summary>
|
|||
public T Value { get; set; } |
|||
|
|||
/// <summary></summary>
|
|||
public bool HasValue { get { return Value != null; } } |
|||
/// <summary></summary>
|
|||
public bool IsNull { get { return Value == null; } } |
|||
|
|||
/// <summary>创建默认值。</summary>
|
|||
public Class(T value = default, bool tryEquals = true, bool tryHashCode = true) |
|||
{ |
|||
Value = value; |
|||
_hashcode = tryHashCode; |
|||
_equals = tryEquals; |
|||
} |
|||
/// <summary></summary>
|
|||
public bool HasValue { get { return Value != null; } } |
|||
|
|||
#region Override
|
|||
/// <summary>创建默认值。</summary>
|
|||
public Class(T value = default, bool tryEquals = true, bool tryHashCode = true) |
|||
{ |
|||
Value = value; |
|||
_hashcode = tryHashCode; |
|||
_equals = tryEquals; |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public override int GetHashCode() |
|||
{ |
|||
if (_hashcode && Value != null) |
|||
#region Override
|
|||
|
|||
/// <summary></summary>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return Value.GetHashCode(); |
|||
if (_hashcode && Value != null) |
|||
{ |
|||
return Value.GetHashCode(); |
|||
} |
|||
return base.GetHashCode(); |
|||
} |
|||
return base.GetHashCode(); |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (_equals) |
|||
/// <summary></summary>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
var right = obj as Class<T>; |
|||
if (right == null) return false; |
|||
if (_equals) |
|||
{ |
|||
var right = obj as Class<T>; |
|||
if (right == null) return false; |
|||
|
|||
if (Value == null && right.Value == null) return true; |
|||
if (Value == null && right.Value != null) return false; |
|||
if (Value != null && right.Value == null) return false; |
|||
return Value.Equals(right.Value); |
|||
} |
|||
return base.Equals(obj); |
|||
} |
|||
|
|||
if (Value == null && right.Value == null) return true; |
|||
if (Value == null && right.Value != null) return false; |
|||
if (Value != null && right.Value == null) return false; |
|||
return Value.Equals(right.Value); |
|||
/// <summary></summary>
|
|||
public override string ToString() |
|||
{ |
|||
if (Value == null) return ""; |
|||
return Value.ToString(); |
|||
} |
|||
return base.Equals(obj); |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
public override string ToString() |
|||
{ |
|||
if (Value == null) return ""; |
|||
return Value.ToString(); |
|||
} |
|||
#endregion
|
|||
|
|||
#endregion
|
|||
#region IComparable
|
|||
|
|||
#region IComparable
|
|||
/// <summary></summary>
|
|||
/// <exception cref="MissingMemberException"></exception>
|
|||
/// <exception cref="NotSupportedException"></exception>
|
|||
public int CompareTo(object obj) |
|||
{ |
|||
if (obj != null && obj is T) return CompareTo((T)obj); |
|||
if (obj != null && obj is Class<T>) return CompareTo(obj as Class<T>); |
|||
|
|||
/// <summary></summary>
|
|||
/// <exception cref="MissingMemberException"></exception>
|
|||
/// <exception cref="NotSupportedException"></exception>
|
|||
public int CompareTo(object obj) |
|||
{ |
|||
if (obj != null && obj is T) return CompareTo((T)obj); |
|||
if (obj != null && obj is Class<T>) return CompareTo(obj as Class<T>); |
|||
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value)); |
|||
if (!(Value is IComparable)) throw new NotSupportedException(); |
|||
return ((IComparable)Value).CompareTo(obj); |
|||
} |
|||
|
|||
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value)); |
|||
if (!(Value is IComparable)) throw new NotSupportedException(); |
|||
return ((IComparable)Value).CompareTo(obj); |
|||
} |
|||
/// <summary></summary>
|
|||
/// <exception cref="MissingMemberException"></exception>
|
|||
/// <exception cref="NotSupportedException"></exception>
|
|||
public int CompareTo(T other) |
|||
{ |
|||
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value)); |
|||
if (!(Value is IComparable)) throw new NotSupportedException(); |
|||
return ((IComparable<T>)Value).CompareTo(other); |
|||
} |
|||
|
|||
/// <summary></summary>
|
|||
/// <exception cref="MissingMemberException"></exception>
|
|||
/// <exception cref="NotSupportedException"></exception>
|
|||
public int CompareTo(T other) |
|||
{ |
|||
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value)); |
|||
if (!(Value is IComparable)) throw new NotSupportedException(); |
|||
return ((IComparable<T>)Value).CompareTo(other); |
|||
} |
|||
/// <summary></summary>
|
|||
/// <exception cref="MissingMemberException"></exception>
|
|||
/// <exception cref="NotSupportedException"></exception>
|
|||
public int CompareTo(Class<T> other) |
|||
{ |
|||
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value)); |
|||
if (other == null || !other.HasValue) return 1; |
|||
|
|||
/// <summary></summary>
|
|||
/// <exception cref="MissingMemberException"></exception>
|
|||
/// <exception cref="NotSupportedException"></exception>
|
|||
public int CompareTo(Class<T> other) |
|||
{ |
|||
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value)); |
|||
if (other == null || !other.HasValue) return 1; |
|||
if (Value is IComparable) return ((IComparable)Value).CompareTo(other.Value); |
|||
if (Value is IComparable<T>) return ((IComparable<T>)Value).CompareTo(other.Value); |
|||
|
|||
if (Value is IComparable) return ((IComparable)Value).CompareTo(other.Value); |
|||
if (Value is IComparable<T>) return ((IComparable<T>)Value).CompareTo(other.Value); |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
throw new NotSupportedException(); |
|||
} |
|||
#endregion
|
|||
|
|||
#endregion
|
|||
#region 运算符。
|
|||
|
|||
#region 运算符。
|
|||
/// <summary>从 <see cref="Class{T}"/> 到 Boolean 的隐式转换,判断 <see cref="Class{T}"/> 包含值。</summary>
|
|||
/// <remarks>当 T 是 Boolean 时,获取 Value。<br />当 T 是 String 时,判断 Value 不为 NULL 且不为空。</remarks>
|
|||
public static implicit operator bool(Class<T> instance) |
|||
{ |
|||
if (instance == null) return false; |
|||
|
|||
/// <summary>从 Class<T> 到 Boolean 的隐式转换,判断 Class<T> 包含值。</summary>
|
|||
/// <remarks>当 T 是 Boolean 时,获取 Value。<br />当 T 是 String 时,判断 Value 不为 NULL 且不为空。</remarks>
|
|||
public static implicit operator bool(Class<T> instance) |
|||
{ |
|||
if (instance == null) return false; |
|||
var boolean = instance as Class<bool>; |
|||
if (boolean != null) return boolean.Value; |
|||
|
|||
var boolean = instance as Class<bool>; |
|||
if (boolean != null) return boolean.Value; |
|||
var text = instance as Class<string>; |
|||
if (text != null) return !string.IsNullOrEmpty(text.Value); |
|||
|
|||
var text = instance as Class<string>; |
|||
if (text != null) return !string.IsNullOrEmpty(text.Value); |
|||
return instance.HasValue; |
|||
} |
|||
|
|||
return instance.HasValue; |
|||
} |
|||
/// <summary>从 <see cref="Class{T}"/> 到 T 的隐式转换。</summary>
|
|||
public static implicit operator T(Class<T> instance) |
|||
{ |
|||
if (instance == null) return default(T); |
|||
|
|||
if (typeof(T).Equals(typeof(bool))) return instance.Value; |
|||
return instance.Value; |
|||
} |
|||
|
|||
// /// <summary>从 Class<T> 到 T 的隐式转换。</summary>
|
|||
// public static implicit operator T(Class<T> instance) => instance == null ? default : instance.Value;
|
|||
/// <summary>从 T 到 <see cref="Class{T}"/> 的隐式转换。</summary>
|
|||
public static implicit operator Class<T>(T value) => new Class<T>(value); |
|||
|
|||
// /// <summary>从 T 到 Class<T> 的隐式转换。</summary>
|
|||
// public static implicit operator Class<T>(T value) => new Class<T>(value);
|
|||
#endregion
|
|||
|
|||
#endregion
|
|||
} |
|||
|
|||
} |
|||
} |
Loading…
Reference in new issue