diff --git a/Apewer.Source/Source/DbClient.cs b/Apewer.Source/Source/DbClient.cs index b1a90e9..c9c7031 100644 --- a/Apewer.Source/Source/DbClient.cs +++ b/Apewer.Source/Source/DbClient.cs @@ -56,6 +56,7 @@ namespace Apewer.Source { Logger.Error(this, "Connect", ex.Message); Close(); + if (ThrowAdoException) throw; return ex.Message; } } @@ -63,7 +64,11 @@ namespace Apewer.Source /// 更改已打开的数据库。 public string Change(string store) { - if (store.IsEmpty()) return "未指定数据名称。"; + if (store.IsEmpty()) + { + if (ThrowAdoException) throw new ArgumentNullException(nameof(store)); + return "未指定数据名称。"; + } var connect = Connect(); if (connect.NotEmpty()) return connect; @@ -76,6 +81,7 @@ namespace Apewer.Source catch (Exception ex) { Logger.Error(this, "Change", ex.Message); + if (ThrowAdoException) throw; return ex.Message(); } } @@ -109,6 +115,8 @@ namespace Apewer.Source #region Transaction + const string TransactionNotFound = "事务不存在。"; + private IDbTransaction _transaction = null; private bool _autocommit = false; @@ -117,9 +125,16 @@ namespace Apewer.Source string Begin(bool commit, Class isolation) { - if (_transaction != null) return "已存在未完成的事务,无法再次启动。"; + if (_transaction != null) + { + const string msg = "已存在未完成的事务,无法再次启动。"; + if (ThrowAdoException) throw new SqlException(msg); + return msg; + } + var connect = Connect(); if (connect.NotEmpty()) return $"无法启动事务:连接失败。(${connect})"; + try { _transaction = isolation ? _conn.BeginTransaction(isolation.Value) : _conn.BeginTransaction(); @@ -129,6 +144,7 @@ namespace Apewer.Source catch (Exception ex) { Logger.Error(this, "Begin", ex.Message()); + if (ThrowAdoException) throw; return ex.Message(); } } @@ -163,7 +179,12 @@ namespace Apewer.Source /// 提交事务。 public string Commit() { - if (_transaction == null) return "事务不存在。"; + if (_transaction == null) + { + if (ThrowAdoException) throw new SqlException(TransactionNotFound); + return TransactionNotFound; + } + try { _transaction.Commit(); @@ -176,6 +197,7 @@ namespace Apewer.Source RuntimeUtility.Dispose(_transaction); _transaction = null; Logger.Error(this, "Commit", ex.Message()); + if (ThrowAdoException) throw; return ex.Message(); } } @@ -183,7 +205,13 @@ namespace Apewer.Source /// 从挂起状态回滚事务。 public string Rollback() { - if (_transaction == null) return "事务不存在。"; + if (_transaction == null) + { + if (ThrowAdoException) throw new SqlException(TransactionNotFound); + return TransactionNotFound; + } + + try { _transaction.Rollback(); @@ -196,6 +224,7 @@ namespace Apewer.Source RuntimeUtility.Dispose(_transaction); _transaction = null; Logger.Error(this, "Rollback", ex.Message()); + if (ThrowAdoException) throw; return ex.Message(); } } @@ -204,6 +233,10 @@ namespace Apewer.Source #region ADO + /// 允许 ADO 抛出异常,取代返回错误信息。 + /// FALSE(默认值) + public virtual bool ThrowAdoException { get; set; } + /// 查询。 /// SQL 语句。 /// 为 SQL 语句提供的参数。 @@ -262,6 +295,7 @@ namespace Apewer.Source catch (Exception exception) { Logger.Error(this, "Query", exception.Message, sql); + if (ThrowAdoException) throw exception; return new Query(exception); } } @@ -329,6 +363,7 @@ namespace Apewer.Source { Logger.Error(this, "Execute", exception.Message, sql); if (tempTran) Rollback(); + if (ThrowAdoException) throw exception; return new Execute(exception); } } diff --git a/Apewer.Source/Source/MySql.cs b/Apewer.Source/Source/MySql.cs index 39cc02d..323ba21 100644 --- a/Apewer.Source/Source/MySql.cs +++ b/Apewer.Source/Source/MySql.cs @@ -51,6 +51,18 @@ namespace Apewer.Source _connstr = cs; } + /// 构建连接字符串以创建实例。 + /// + public MySql(string address, int port, string store, string user, string pass, Timeout timeout = null) : base(timeout) + { + if (string.IsNullOrEmpty(address)) throw new ArgumentNullException(nameof(address)); + if (port < 1 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port)); + if (string.IsNullOrEmpty(store)) store = "mysql"; + if (string.IsNullOrEmpty(user)) user = "root"; + var cs = $"server={address}; port={port}; database={store}; uid={user}; pwd={pass ?? ""}; "; + _connstr = cs; + } + /// protected override void ClearPool(bool all = false) { diff --git a/Apewer.Source/Source/SqlClient.cs b/Apewer.Source/Source/SqlClient.cs index db2261a..d2a2299 100644 --- a/Apewer.Source/Source/SqlClient.cs +++ b/Apewer.Source/Source/SqlClient.cs @@ -420,7 +420,7 @@ COLLATE Chinese_PRC_CI_AS if (!create.Success) return TextUtility.Merge("创建失败:", create.Message); // 设置兼容性级别。 - var sql2 = $"alter database [{store}] set compatibility_level = 0"; + var sql2 = $"alter database [{store}] set compatibility_level = 100"; source.Execute(sql2, null, false); // 设置恢复默认为“简单” diff --git a/Apewer.Source/Source/Sqlite.cs b/Apewer.Source/Source/Sqlite.cs index 5405562..989f02b 100644 --- a/Apewer.Source/Source/Sqlite.cs +++ b/Apewer.Source/Source/Sqlite.cs @@ -50,6 +50,10 @@ namespace Apewer.Source /// 连接数据库的密码,使用内存数据库时此参数将被忽略。 /// 超时设定。 /// + /// + /// + /// + /// public Sqlite(string path = null, string pass = null, Timeout timeout = null) : base(timeout) { // 使用内存。 @@ -61,7 +65,12 @@ namespace Apewer.Source } // 使用文件。 - if (!File.Exists(path)) throw new FileNotFoundException("文件不存在。", path); + if (!File.Exists(path)) + { + var dir = Directory.GetParent(path).FullName; + if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); + File.Create(path).Dispose(); + } _connstr = $"data source='{path}'; version=3; "; _path = path; if (!string.IsNullOrEmpty(pass)) @@ -322,31 +331,32 @@ namespace Apewer.Source #region mirror /// 保存当前数据库到文件,若文件已存在则将重写文件。 - public bool Save(string path, string pass = null) + public string Save(string path, string pass = null) { + if (path.IsEmpty()) return "参数 path 无效。"; if (!StorageUtility.CreateFile(path, 0, true)) { - Logger.Error(nameof(Sqlite), "Save", TextUtility.Merge("创建文件 ", path, " 失败。")); - return false; + var msg = $"创建文件 {path} 失败。"; + Logger.Error(nameof(Sqlite), "Save", msg); + return msg; } - using (var destination = new Sqlite(path, pass)) return Save(destination); } /// 保存当前数据库到目标数据库。 - public bool Save(Sqlite destination) => string.IsNullOrEmpty(Backup(this, destination)); + public string Save(Sqlite destination) => Backup(this, destination); /// 加载文件到当前数据库。 - public bool Load(string path, string pass = null) + public string Load(string path, string pass = null) { using (var source = new Sqlite(path, pass)) return Load(source); } /// 加载源数据库到当前数据库。 - public bool Load(Sqlite source) => string.IsNullOrEmpty(Backup(source, this)); + public string Load(Sqlite source) => Backup(source, this); /// 备份数据库,返回错误信息。 - static string Backup(Sqlite source, Sqlite destination) + string Backup(Sqlite source, Sqlite destination) { if (source == null) return "备份失败:源无效。"; if (destination == null) return "备份失败:目标无效。"; @@ -355,22 +365,18 @@ namespace Apewer.Source var dConnect = source.Connect(); if (dConnect.NotEmpty()) return dConnect; - lock (source.Locker) + try { - lock (destination.Locker) - { - try - { - var src = (SQLiteConnection)source.Connection; - var dst = (SQLiteConnection)destination.Connection; - src.BackupDatabase(dst, "main", "main", -1, null, 0); - return ""; - } - catch (Exception ex) - { - return "SQLite Load Failed: " + ex.Message; - } - } + var src = (SQLiteConnection)source.Connection; + var dst = (SQLiteConnection)destination.Connection; + src.BackupDatabase(dst, "main", "main", -1, null, 0); + return ""; + } + catch (Exception ex) + { + var msg = "备份失败:" + ex.Message; + Logger?.Error(this, msg); + return msg; } } diff --git a/Apewer.Windows/Internals/RegHelper.cs b/Apewer.Windows/Internals/RegHelper.cs new file mode 100644 index 0000000..d24d190 --- /dev/null +++ b/Apewer.Windows/Internals/RegHelper.cs @@ -0,0 +1,118 @@ +using Apewer; +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace Apewer.Internals +{ + + /// 注册表。 + static class RegHelper + { + + /// 用户登录后的启动项。 + const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run"; + + /// HKEY_CURRENT_USER + /// 当前用户的信息。 + static RegistryKey CurrentUser { get => Registry.CurrentUser; } + + /// HKEY_LOCAL_MACHINE + /// 系统信息,对所有用户生效,设置需要管理员权限。 + static RegistryKey LocalMachine { get => Registry.LocalMachine; } + + static RegistryKey OpenSubKey(RegistryKey root, string key, bool write = false) + { + var segs = key.Split('\\', '/'); + var queue = new Queue(segs); + var rkey = root; + var check = write ? RegistryKeyPermissionCheck.ReadWriteSubTree : RegistryKeyPermissionCheck.ReadSubTree; + while (queue.Count > 0) + { + var name = queue.Dequeue(); + var sub = rkey.OpenSubKey(name, check); + if (sub == null) + { + if (!write) return null; + rkey = rkey.CreateSubKey(name); + } + else + { + rkey = sub; + } + } + return rkey; + } + + /// 获取字符串。 + /// 注册表存储区。 + /// 路径。 + /// 名称。 + /// 字符串的值。获取失败时返回 NULL 值。 + static string Get(RegistryKey root, string key, string name) + { + try + { + var rkey = OpenSubKey(root, key, false); + if (rkey == null) return null; + + var names = rkey.GetValueNames().ToList(); + if (names.Contains(name)) + { + var obj = rkey.GetValue(name, null); + var str = obj as string; + return str; + } + } + catch { } + return null; + } + + /// 设置字符串,指定 value 为 NULL 可删除该值。 + /// 注册表存储区。 + /// 路径。 + /// 名称。 + /// 值。 + /// 错误信息。设置成功时返回 NULL 值。 + static string Set(RegistryKey root, string key, string name, string value) + { + try + { + var rkey = OpenSubKey(root, key, true); + if (rkey == null) return "无法打开子键。"; + + var apps = rkey.GetValueNames(); + if (string.IsNullOrEmpty(value)) rkey.DeleteValue(name, true); + else rkey.SetValue(name, value, RegistryValueKind.String); + return null; + } + catch (Exception ex) + { + return ex.Message; + } + } + + /// 已启用自动启动。 + public static bool AutoRun + { + get + { + var exePath = Application.ExecutablePath; + var exeName = Path.GetFileNameWithoutExtension(exePath); + return Get(CurrentUser, RunKey, exeName) == exePath; + } + set + { + var exePath = Application.ExecutablePath; + var exeName = Path.GetFileNameWithoutExtension(exePath); + Set(CurrentUser, RunKey, exeName, value ? exePath : null); + } + } + + } + +} diff --git a/Apewer.Windows/Surface/FormsUtility.cs b/Apewer.Windows/Surface/FormsUtility.cs index 38849e4..69e78b6 100644 --- a/Apewer.Windows/Surface/FormsUtility.cs +++ b/Apewer.Windows/Surface/FormsUtility.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Diagnostics; #if NETFX || NETCORE +using Microsoft.Win32; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; @@ -829,14 +830,12 @@ namespace Apewer.Surface private const string FontYahei = "Microsoft Yahei"; private const string FontSimsun = "Simsun"; + private const string GuiFontRegKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\GRE_Initialize"; private static string _fontname = ""; /// 存在微软雅黑字体。 - public static bool MsyhExist - { - get { return File.Exists("c:\\windows\\fonts\\msyh.ttc"); } - } + public static bool MsyhExist { get => File.Exists("c:\\windows\\fonts\\msyh.ttc"); } /// 获取默认字体名称。 public static string DefaultFontName @@ -853,8 +852,17 @@ namespace Apewer.Surface } } + /// 获取默认字体名称。 + public static string GuiFontName { get => Registry.GetValue(GuiFontRegKey, "GUIFont.Facename", "Arial") as string; } + + /// 获取默认字体大小。 + public static float GuiFontSize { get => Convert.ToSingle(Registry.GetValue(GuiFontRegKey, "GUIFont.Height", 9F)); } + #if NETFX || NETCORE + /// 获取默认字体。 + public static Font GuiFont { get => new Font(GuiFontName, GuiFontSize); } + /// 获取默认字体。 public static Font DefaultFont { diff --git a/Apewer.Windows/Surface/ImageUtility.cs b/Apewer.Windows/Surface/ImageUtility.cs index 81b8798..fa48253 100644 --- a/Apewer.Windows/Surface/ImageUtility.cs +++ b/Apewer.Windows/Surface/ImageUtility.cs @@ -7,6 +7,7 @@ using Apewer.Models; using System; using System.Collections.Generic; using System.Drawing; +using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Text; @@ -15,7 +16,7 @@ namespace Apewer.Surface { /// 图像实用工具。 - public sealed class ImageUtility + public static class ImageUtility { internal static byte[] EmptyBytes { get { return new byte[0]; } } @@ -50,6 +51,58 @@ namespace Apewer.Surface return SaveAsBytes(image, ImageFormat.Jpeg, dispose); } + /// 调整图像尺寸,生成新图像。 + /// 原图像。 + /// 新图像的宽度,在缩放时此参数用于限定最大宽度。 + /// 新图像的高度,在缩放时此参数用于限定最大高度。 + /// 保持原比例进行缩放。 + /// 新图像。 + public static Bitmap Resize(this Image image, int width, int height, bool scale = false) + { + if (image == null) throw new ArgumentNullException(nameof(image)); + if (width < 0) throw new ArgumentOutOfRangeException(nameof(width)); + if (height < 0) throw new ArgumentOutOfRangeException(nameof(height)); + + var newWidth = width; + var newHeight = height; + if (scale) + { + newWidth = image.Width; + newHeight = image.Height; + var radio = Convert.ToDouble(newWidth) / Convert.ToDouble(newHeight); + if (newWidth > width) + { + newWidth = width; + newHeight = Convert.ToInt32(width / radio); + } + if (newHeight > height) + { + newWidth = Convert.ToInt32(height * radio); + newHeight = height; + } + } + + var bitmap = new Bitmap(newWidth, newHeight); + try + { + using (var graphic = Graphics.FromImage(bitmap)) + { + graphic.CompositingMode = CompositingMode.SourceOver; + graphic.CompositingQuality = CompositingQuality.HighQuality; + graphic.SmoothingMode = SmoothingMode.AntiAlias; + graphic.Clear(Color.Transparent); + graphic.DrawImage(image, 0, 0, width, height); + } + + return bitmap; + } + catch (Exception ex) + { + RuntimeUtility.Dispose(bitmap); + throw ex; + } + } + /// 生成二维码图像,可指定色块边长像素值和纠错率,失败时返回 NULL 值。 public static Bitmap GenerateQrCode(string text, int block = 8) { diff --git a/Apewer/Apewer.props b/Apewer/Apewer.props index 1d64731..edd434c 100644 --- a/Apewer/Apewer.props +++ b/Apewer/Apewer.props @@ -9,7 +9,7 @@ Apewer Apewer Libraries - 6.7.2 + 6.7.3 diff --git a/Apewer/BytesUtility.cs b/Apewer/BytesUtility.cs index 454d171..cbb86ee 100644 --- a/Apewer/BytesUtility.cs +++ b/Apewer/BytesUtility.cs @@ -776,6 +776,36 @@ namespace Apewer #region AES + private static void Aes256(byte[] key, byte[] salt, Func create, Stream input, Stream output) + { + using (var rm = new RijndaelManaged()) + { + rm.KeySize = 256; + rm.BlockSize = 128; + rm.Mode = CipherMode.ECB; + rm.Padding = PaddingMode.PKCS7; + + var k = new Rfc2898DeriveBytes(SHA256(key), salt, 1); + rm.Key = k.GetBytes(32); + rm.IV = k.GetBytes(16); + + using (var ct = create.Invoke(rm)) + { + using (var cs = new CryptoStream(output, ct, CryptoStreamMode.Write)) + { + Read(input, cs); + cs.Close(); + } + } + } + } + + /// 执行 AES 加密。 + public static void Aes256Encrypt(Stream input, Stream output, byte[] key) => Aes256(key, key, rm => rm.CreateEncryptor(), input, output); + + /// 执行 AES 解密。 + public static void Aes256Decrypt(Stream input, Stream output, byte[] key) => Aes256(key, key, rm => rm.CreateDecryptor(), input, output); + private static RijndaelManaged Aes256Provider(byte[] key) { var k = key ?? Empty; // AesFill(key); diff --git a/Apewer/Class.cs b/Apewer/Class.cs index 0797b45..b3ea296 100644 --- a/Apewer/Class.cs +++ b/Apewer/Class.cs @@ -13,12 +13,6 @@ namespace Apewer /// 装箱对象。 public T Value { get; set; } - /// - public bool IsNull { get { return Value == null; } } - - /// - public bool HasValue { get { return Value != null; } } - /// 创建默认值。 public Class(T value = default, bool tryEquals = true, bool tryHashCode = true) { @@ -95,7 +89,6 @@ namespace Apewer public int CompareTo(Class 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) return ((IComparable)Value).CompareTo(other.Value); @@ -119,7 +112,7 @@ namespace Apewer var text = instance as Class; if (text != null) return !string.IsNullOrEmpty(text.Value); - return instance.HasValue; + return instance.NotNull(); } /// 到 T 的隐式转换。 diff --git a/Apewer/ClockUtility.cs b/Apewer/ClockUtility.cs index 15c46b2..e4fb138 100644 --- a/Apewer/ClockUtility.cs +++ b/Apewer/ClockUtility.cs @@ -5,6 +5,8 @@ using System.Diagnostics; using System.Globalization; using System.Text; +using static System.DateTime; + namespace Apewer { @@ -12,6 +14,29 @@ namespace Apewer public static class ClockUtility { + #region DateTime + + /// 尝试转换内容为 DateTime 实例。 + public static Class DateTime(object value) + { + if (value is DateTime dt) return dt; + if (value.IsNull()) return null; + + DateTime result; + try + { + var text = value.ToString(); + var parsed = System.DateTime.TryParse(text, out result); + return parsed ? new Class(result) : null; + } + catch + { + return null; + } + } + + #endregion + #region Fixed private static DateTime _zero = new DateTime(0L, DateTimeKind.Unspecified); @@ -28,10 +53,10 @@ namespace Apewer public static DateTime UtcOrigin { get => _utc_origin; } /// 获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为本地时间。 - public static DateTime Now { get => DateTime.Now; } + public static DateTime Now { get => System.DateTime.Now; } /// 获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为协调通用时间 (UTC)。 - public static DateTime UtcNow { get => DateTime.UtcNow; } + public static DateTime UtcNow { get => System.DateTime.UtcNow; } /// 创建一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000。 public static DateTime NewOrigin(DateTimeKind kind) => new DateTime(1970, 1, 1, 0, 0, 0, 0, kind); @@ -94,7 +119,7 @@ namespace Apewer var s = datetime as string; if (string.IsNullOrEmpty(s)) return Zero; DateTime value; - var success = DateTime.TryParse(s, out value); + var success = TryParse(s, out value); return success ? value : Zero; } else @@ -166,11 +191,11 @@ namespace Apewer } DateTime dt; - if (!DateTime.TryParse(str, out dt)) + if (!TryParse(str, out dt)) { - if (!str.Contains("-") && DateTime.TryParseExact(str, "yyyy-M-d", null, DateTimeStyles.None, out dt)) + if (!str.Contains("-") && TryParseExact(str, "yyyy-M-d", null, DateTimeStyles.None, out dt)) { - if (!str.Contains("/") && DateTime.TryParseExact(str, "yyyy/M/d", null, DateTimeStyles.None, out dt)) + if (!str.Contains("/") && TryParseExact(str, "yyyy/M/d", null, DateTimeStyles.None, out dt)) { return null; } @@ -192,7 +217,7 @@ namespace Apewer public static string LucidUtc { get => Lucid(UtcNow); } /// 表示当前本地日期的文本,显示为易于阅读的格式。 - public static string LucidDate { get { return Lucid(DateTime.Now, true, false, false, false); } } + public static string LucidDate { get { return Lucid(Now, true, false, false, false); } } /// 表示当前本地时间的文本,显示为紧凑的格式。 public static string CompactNow { get => Compact(Now); } @@ -201,7 +226,7 @@ namespace Apewer public static string CompactUtc { get => Compact(UtcNow); } /// 表示当前本地日期的文本,显示为紧凑的格式。 - public static string CompactDate { get { return Compact(DateTime.Now, true, false, false, false); } } + public static string CompactDate { get { return Compact(Now, true, false, false, false); } } /// 转换 DateTime 对象到易于阅读的文本。 public static string Lucid(DateTime datetime, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) @@ -305,7 +330,7 @@ namespace Apewer // 使用默认解析。 { - if (DateTime.TryParse(text, out DateTime dt)) return dt; + if (TryParse(text, out DateTime dt)) return dt; } // 尝试解析 Lucid 格式。 @@ -332,7 +357,7 @@ namespace Apewer day = NumberUtility.Int32(lucid.Substring(8, 2)); if (year < 1) return failed; if (month < 1 || month > 12) return failed; - if (day < 1 || day > DateTime.DaysInMonth(year, month)) return failed; + if (day < 1 || day > DaysInMonth(year, month)) return failed; int hour = 0, minute = 0, second = 0, milli = 0; if (lucid.Length >= 16) diff --git a/Apewer/CollectionUtility.cs b/Apewer/CollectionUtility.cs index d9903f5..29d530e 100644 --- a/Apewer/CollectionUtility.cs +++ b/Apewer/CollectionUtility.cs @@ -577,6 +577,232 @@ namespace Apewer #endregion + #region Find + + /// 根据条件筛选,将符合条件的元素组成新数组。 + public static T[] FindAll(this T[] array, Predicate match) => System.Array.FindAll(array, match); + + /// 根据条件筛选,找到第一个符合条件的元素。 + public static T Find(this T[] array, Predicate match) => System.Array.Find(array, match); + + #endregion + + #region Map + + /// 遍历集合,转换元素,生成新数组。 + /// 输入的元素类型。 + /// 输出的元素类型。 + /// 要遍历的集合。 + /// 转换程序。 + /// 新数组的元素类型。 + public static TOut[] Map(this IEnumerable collection, Func selector) + { + if (selector == null) throw new ArgumentNullException(nameof(selector)); + if (collection == null) return new TOut[0]; + + if (collection is TIn[] array) + { + var count = array.Length; + var result = new TOut[count]; + for (var i = 0; i < count; i++) + { + result[i] = selector.Invoke(array[i]); + } + return result; + } + else + { + var capacity = 0; + var count = 0; + var list = new List(); + foreach (var item in collection) + { + if (capacity == count) + { + capacity += 1024; + list.Capacity += capacity; + } + list.Add(selector.Invoke(item)); + count += 1; + } + return list.ToArray(); + } + } + + #endregion + + #region ForEach + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 回调。参数为本次遍历的元素。 + public static void ForEach(this IEnumerable collection, Action callback) + { + if (collection == null || callback == null) return; + ForEach(collection, (item, index) => + { + callback.Invoke(item); + return true; + }); + } + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。 + public static void ForEach(this IEnumerable collection, Action callback) + { + if (collection == null || callback == null) return; + ForEach(collection, (item, index) => + { + callback.Invoke(item, index); + return true; + }); + } + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 回调。参数为本次遍历的元素。返回 true 将继续遍历,返回 false 打断遍历。 + public static void ForEach(this IEnumerable collection, Func callback) + { + if (collection == null || callback == null) return; + ForEach(collection, (item, index) => + { + var result = callback.Invoke(item); + return result; + }); + } + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。返回 true 将继续遍历,返回 false 打断遍历。 + public static void ForEach(this IEnumerable collection, Func callback) + { + if (collection == null || callback == null) return; + + if (collection is T[] array) + { + var count = array.Length; + for (var i = 0; i < count; i++) + { + var @continue = callback.Invoke(array[i], i); + if (!@continue) break; + } + } + else + { + var index = 0; + foreach (var item in collection) + { + var @continue = callback.Invoke(item, index); + if (!@continue) break; + index += 1; + } + } + } + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 每次遍历的元素数量。 + /// 回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。 + public static void ForEach(this IEnumerable collection, int limit, Action callback) + { + if (collection == null || callback == null) return; + + ForEach(collection, limit, (items, index) => + { + callback.Invoke(items); + return true; + }); + } + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 每次遍历的元素数量。 + /// 回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。 + public static void ForEach(this IEnumerable collection, int limit, Action callback) + { + if (collection == null || callback == null) return; + + ForEach(collection, limit, (items, index) => + { + callback.Invoke(items, index); + return true; + }); + } + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 每次遍历的元素数量。 + /// 回调。参数为本次遍历的元素。返回 true 将继续遍历,返回 false 打断遍历。 + public static void ForEach(this IEnumerable collection, int limit, Func callback) + { + if (collection == null || callback == null) return; + + ForEach(collection, limit, (items, index) => + { + var result = callback.Invoke(items); + return result; + }); + } + + /// 遍历每个元素,执行回调。 + /// 元素的类型。 + /// 元素的集合。 + /// 每次遍历的元素数量。 + /// 回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。返回 true 将继续遍历,返回 false 打断遍历。 + public static void ForEach(this IEnumerable collection, int limit, Func callback) + { + if (limit < 1) throw new ArgumentOutOfRangeException(nameof(limit)); + if (collection == null) return; + if (callback == null) return; + + if (collection is T[] array) + { + var total = array.LongLength; + var start = 0L; + var index = 0; + var length = 0L; + while (start < total) + { + length = limit; + if (start + length > total) length = total - start; + var temp = new T[length]; + System.Array.Copy(array, start, temp, 0L, length); + var @continue = callback.Invoke(temp, index); + if (!@continue) break; + + start += length; + index += 1; + } + return; + } + else + { + var queue = new Queue(collection); + var index = 0; + while (queue.Count > 0) + { + var list = new List(limit); + while (list.Count < limit && queue.Count > 0) list.Add(queue.Dequeue()); + + var temp = list.ToArray(); + var @continue = callback.Invoke(temp, index); + if (!@continue) break; + + index += 1; + } + } + } + + #endregion + } } diff --git a/Apewer/Internals/NtfsUnlocker.cs b/Apewer/Internals/NtfsUnlocker.cs index 8aad3ee..3f3e5f0 100644 --- a/Apewer/Internals/NtfsUnlocker.cs +++ b/Apewer/Internals/NtfsUnlocker.cs @@ -22,10 +22,12 @@ namespace Apewer.Internals [return: MarshalAs(UnmanagedType.Bool)] static extern bool DeleteFile(string name); - static bool FileExists(string path) => GetFileAttributes(path) != -1; - #endregion + public const string Postfix = ":Zone.Identifier"; + + public static bool FileExists(string path) => GetFileAttributes(path) != -1; + public static string DeleteZoneIdentifier(string path) { if (!FileExists(path)) return "文件不存在。"; @@ -33,7 +35,7 @@ namespace Apewer.Internals try { var info = new FileInfo(path); - var streamPath = info.FullName + ":Zone.Identifier"; + var streamPath = info.FullName + Postfix; var streamExists = FileExists(streamPath); if (!streamExists) return null; diff --git a/Apewer/Json.cs b/Apewer/Json.cs index a59f9b7..605e934 100644 --- a/Apewer/Json.cs +++ b/Apewer/Json.cs @@ -38,7 +38,7 @@ namespace Apewer [NonSerialized] private static bool _forceall = true; - /// 允许产生 Exception,默认为不允许。 + /// 允许抛出 Exception,默认为不允许。 public static bool AllowException { get { return _throw; } set { _throw = value; } } /// 当存在递归引用时候包含递归项。指定为 True 时递归项为 Null 值,指定为 False 时不包含递归项。默认值:False。 @@ -1174,7 +1174,7 @@ namespace Apewer if (list is Array array) { if (array != null && array.Rank > 1) - { + { ToJson(json, array, array.Rank, new int[0]); return json; } @@ -1200,6 +1200,13 @@ namespace Apewer } if (recursively) continue; + // value 是 null。 + if (value.IsNull()) + { + json.AddItem(); + continue; + } + // 处理 Type 对象。 if (value.GetType().Equals(typeof(Type)) && (previous.Length > 2)) value = FullName((Type)value); @@ -1207,7 +1214,7 @@ namespace Apewer if (value.GetType().Equals(typeof(Assembly)) && (previous.Length > 2)) value = ((Assembly)value).FullName; if (value == null) { json.AddItem(); } - else if (value is DateTime) { json.AddItem(value.ToString()); } + else if (value is DateTime dt) { json.AddItem(SerializeDateTime(dt)); } else if (value is bool) { json.AddItem((bool)value); } else if (value is byte) { json.AddItem((byte)value); } else if (value is sbyte) { json.AddItem((sbyte)value); } @@ -1318,7 +1325,7 @@ namespace Apewer } if (value == null) { json.SetProperty(field); } - else if (value is DateTime) { json.SetProperty(field, value.ToString()); } + else if (value is DateTime dt) { json.SetProperty(field, SerializeDateTime(dt)); } else if (value is bool) { json.SetProperty(field, (bool)value); } else if (value is byte) { json.SetProperty(field, (byte)value); } else if (value is sbyte) { json.SetProperty(field, (sbyte)value); } @@ -1740,8 +1747,8 @@ namespace Apewer var subtype = null as Type; if (array is Array) { - string typeName = FullName(array.GetType()).Replace("[]", string.Empty); - subtype = array.GetType().Assembly.GetType(typeName); + var arrayType = array.GetType(); + subtype = RuntimeUtility.GetTypeOfArrayItem(arrayType); } else { @@ -1812,12 +1819,11 @@ namespace Apewer { try { - setter.Invoke(entity, new object[] { DateTime.Parse(value.ToString()) }); + setter.Invoke(entity, new object[] { DeserializeDateTime(value as string) }); } catch (Exception exception) { if (AllowException) throw exception; - setter.Invoke(entity, new object[] { "" }); } } else if (pt.Equals(typeof(string))) @@ -2239,6 +2245,35 @@ namespace Apewer #endregion + #region DateTime + + /// 自定义 DateTime 序列化程序。 + public static Func DateTimeSerializer { get; set; } + + /// 自定义 DateTime 反序列化程序。 + public static Func DateTimeDeserializer { get; set; } + + /// 序列化 DateTime 实例。 + public static string SerializeDateTime(DateTime dateTime) + { + var serializer = DateTimeSerializer; + if (serializer != null) return serializer.Invoke(dateTime); + + return ClockUtility.Lucid(dateTime); + } + + /// 序列化 DateTime 实例。 + public static DateTime DeserializeDateTime(string text) + { + var deserializer = DateTimeDeserializer; + if (deserializer != null) return deserializer.Invoke(text); + + var ndt = ClockUtility.Parse(text); + return ndt == null ? default(DateTime) : ndt.Value; + } + + #endregion + } } diff --git a/Apewer/Network/MailAddress.cs b/Apewer/Network/MailAddress.cs index a899eb2..6c99c37 100644 --- a/Apewer/Network/MailAddress.cs +++ b/Apewer/Network/MailAddress.cs @@ -8,7 +8,6 @@ namespace Apewer.Network { /// 邮件地址。 - [Table("_mail_address")] [Serializable] public sealed class MailAddress : Record { diff --git a/Apewer/Network/MailClient.cs b/Apewer/Network/MailClient.cs index d2d7117..34d0984 100644 --- a/Apewer/Network/MailClient.cs +++ b/Apewer/Network/MailClient.cs @@ -9,7 +9,6 @@ namespace Apewer.Network { /// 简单邮件传输协议客户端。 - [Table("_mail_client")] [Serializable] public sealed class MailClient : Record { diff --git a/Apewer/Network/MailRecord.cs b/Apewer/Network/MailRecord.cs index 5ff9ba4..6ae4ca6 100644 --- a/Apewer/Network/MailRecord.cs +++ b/Apewer/Network/MailRecord.cs @@ -8,7 +8,6 @@ namespace Apewer.Network { /// 邮件记录。 - [Table("_mail_record")] [Serializable] public class MailRecord : Record { diff --git a/Apewer/NumberUtility.cs b/Apewer/NumberUtility.cs index a05c914..32464e0 100644 --- a/Apewer/NumberUtility.cs +++ b/Apewer/NumberUtility.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; using System.Text; +using System.Text.RegularExpressions; namespace Apewer { @@ -164,6 +165,28 @@ namespace Apewer #endregion + #region 表达式计算 + + /// 计算文本表达式,以 Int64 输出结果。 + public static long ComputeInt64(this string expression) + { + var result = Source.SourceUtility.Compute(expression); + if (result == null) return 0L; + if (result is long @long) return @long; + return Int64(result); + } + + /// 计算文本表达式,以 Double 输出结果。 + public static double ComputeDouble(this string expression) + { + var result = Source.SourceUtility.Compute(expression); + if (result == null) return 0D; + if (result is double @double) return @double; + return Double(result); + } + + #endregion + /// 平均值。 public static double Average(params double[] values) { @@ -392,41 +415,243 @@ namespace Apewer return false; } + private static Tout Try(Tin value, Func func) + { + try { return func.Invoke(value); } + catch { return default(Tout); } + } + /// 获取单精度浮点对象。 - public static float Float(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); + public static float Float(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToSingle); + if (number is byte _byte) return Try(_byte, Convert.ToSingle); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToSingle); + if (number is short _short) return Try(_short, Convert.ToSingle); + if (number is ushort _ushort) return Try(_ushort, Convert.ToSingle); + if (number is int _int) return Try(_int, Convert.ToSingle); + if (number is uint _uint) return Try(_uint, Convert.ToSingle); + if (number is long _long) return Try(_long, Convert.ToSingle); + if (number is ulong _ulong) return Try(_ulong, Convert.ToSingle); + if (number is float _float) return Try(_float, Convert.ToSingle); + if (number is double _double) return Try(_double, Convert.ToSingle); + if (number is decimal _decimal) return Try(_decimal, Convert.ToSingle); + return GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); + } /// 获取单精度浮点对象。 - public static float Single(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); + public static float Single(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToSingle); + if (number is byte _byte) return Try(_byte, Convert.ToSingle); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToSingle); + if (number is short _short) return Try(_short, Convert.ToSingle); + if (number is ushort _ushort) return Try(_ushort, Convert.ToSingle); + if (number is int _int) return Try(_int, Convert.ToSingle); + if (number is uint _uint) return Try(_uint, Convert.ToSingle); + if (number is long _long) return Try(_long, Convert.ToSingle); + if (number is ulong _ulong) return Try(_ulong, Convert.ToSingle); + if (number is float _float) return Try(_float, Convert.ToSingle); + if (number is double _double) return Try(_double, Convert.ToSingle); + if (number is decimal _decimal) return Try(_decimal, Convert.ToSingle); + return GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); + } /// 获取双精度浮点对象。 - public static double Double(object number) => GetNumber(number, Convert.ToDouble, (v, d) => v / d); + public static double Double(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToDouble); + if (number is byte _byte) return Try(_byte, Convert.ToDouble); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToDouble); + if (number is short _short) return Try(_short, Convert.ToDouble); + if (number is ushort _ushort) return Try(_ushort, Convert.ToDouble); + if (number is int _int) return Try(_int, Convert.ToDouble); + if (number is uint _uint) return Try(_uint, Convert.ToDouble); + if (number is long _long) return Try(_long, Convert.ToDouble); + if (number is ulong _ulong) return Try(_ulong, Convert.ToDouble); + if (number is float _float) return Try(_float, Convert.ToDouble); + if (number is double _double) return Try(_double, Convert.ToDouble); + if (number is decimal _decimal) return Try(_decimal, Convert.ToDouble); + return GetNumber(number, Convert.ToDouble, (v, d) => v / d); + } /// 获取 Decimal 对象。 - public static decimal Decimal(object number) => GetNumber(number, DecimalAsFloat, (v, d) => v / Convert.ToDecimal(d)); + public static decimal Decimal(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToDecimal); + if (number is byte _byte) return Try(_byte, Convert.ToDecimal); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToDecimal); + if (number is short _short) return Try(_short, Convert.ToDecimal); + if (number is ushort _ushort) return Try(_ushort, Convert.ToDecimal); + if (number is int _int) return Try(_int, Convert.ToDecimal); + if (number is uint _uint) return Try(_uint, Convert.ToDecimal); + if (number is long _long) return Try(_long, Convert.ToDecimal); + if (number is ulong _ulong) return Try(_ulong, Convert.ToDecimal); + if (number is float _float) return Try(_float, Convert.ToDecimal); + if (number is double _double) return Try(_double, Convert.ToDecimal); + if (number is decimal _decimal) return Try(_decimal, Convert.ToDecimal); + return GetNumber(number, DecimalAsFloat, (v, d) => v / Convert.ToDecimal(d)); + } /// 获取 Byte 对象。 - public static byte Byte(object number) => GetNumber(number, Convert.ToByte); + public static byte Byte(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToByte); + if (number is byte _byte) return Try(_byte, Convert.ToByte); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToByte); + if (number is short _short) return Try(_short, Convert.ToByte); + if (number is ushort _ushort) return Try(_ushort, Convert.ToByte); + if (number is int _int) return Try(_int, Convert.ToByte); + if (number is uint _uint) return Try(_uint, Convert.ToByte); + if (number is long _long) return Try(_long, Convert.ToByte); + if (number is ulong _ulong) return Try(_ulong, Convert.ToByte); + if (number is float _float) return Try(_float, Convert.ToByte); + if (number is double _double) return Try(_double, Convert.ToByte); + if (number is decimal _decimal) return Try(_decimal, Convert.ToByte); + return GetNumber(number, Convert.ToByte); + } /// 获取 SByte 对象。 - public static sbyte SByte(object number) => GetNumber(number, Convert.ToSByte); + public static sbyte SByte(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToSByte); + if (number is byte _byte) return Try(_byte, Convert.ToSByte); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToSByte); + if (number is short _short) return Try(_short, Convert.ToSByte); + if (number is ushort _ushort) return Try(_ushort, Convert.ToSByte); + if (number is int _int) return Try(_int, Convert.ToSByte); + if (number is uint _uint) return Try(_uint, Convert.ToSByte); + if (number is long _long) return Try(_long, Convert.ToSByte); + if (number is ulong _ulong) return Try(_ulong, Convert.ToSByte); + if (number is float _float) return Try(_float, Convert.ToSByte); + if (number is double _double) return Try(_double, Convert.ToSByte); + if (number is decimal _decimal) return Try(_decimal, Convert.ToSByte); + return GetNumber(number, Convert.ToSByte); + } /// 获取 Int16 对象。 - public static short Int16(object number) => GetNumber(number, Convert.ToInt16); + public static short Int16(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToInt16); + if (number is byte _byte) return Try(_byte, Convert.ToInt16); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToInt16); + if (number is short _short) return Try(_short, Convert.ToInt16); + if (number is ushort _ushort) return Try(_ushort, Convert.ToInt16); + if (number is int _int) return Try(_int, Convert.ToInt16); + if (number is uint _uint) return Try(_uint, Convert.ToInt16); + if (number is long _long) return Try(_long, Convert.ToInt16); + if (number is ulong _ulong) return Try(_ulong, Convert.ToInt16); + if (number is float _float) return Try(_float, Convert.ToInt16); + if (number is double _double) return Try(_double, Convert.ToInt16); + if (number is decimal _decimal) return Try(_decimal, Convert.ToInt16); + return GetNumber(number, Convert.ToInt16); + } /// 获取 UInt16 对象。 - public static ushort UInt16(object number) => GetNumber(number, Convert.ToUInt16); + public static ushort UInt16(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToUInt16); + if (number is byte _byte) return Try(_byte, Convert.ToUInt16); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToUInt16); + if (number is short _short) return Try(_short, Convert.ToUInt16); + if (number is ushort _ushort) return Try(_ushort, Convert.ToUInt16); + if (number is int _int) return Try(_int, Convert.ToUInt16); + if (number is uint _uint) return Try(_uint, Convert.ToUInt16); + if (number is long _long) return Try(_long, Convert.ToUInt16); + if (number is ulong _ulong) return Try(_ulong, Convert.ToUInt16); + if (number is float _float) return Try(_float, Convert.ToUInt16); + if (number is double _double) return Try(_double, Convert.ToUInt16); + if (number is decimal _decimal) return Try(_decimal, Convert.ToUInt16); + return GetNumber(number, Convert.ToUInt16); + } /// 获取 Int32 对象。 - public static int Int32(object number) => GetNumber(number, Convert.ToInt32); + public static int Int32(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToInt32); + if (number is byte _byte) return Try(_byte, Convert.ToInt32); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToInt32); + if (number is short _short) return Try(_short, Convert.ToInt32); + if (number is ushort _ushort) return Try(_ushort, Convert.ToInt32); + if (number is int _int) return Try(_int, Convert.ToInt32); + if (number is uint _uint) return Try(_uint, Convert.ToInt32); + if (number is long _long) return Try(_long, Convert.ToInt32); + if (number is ulong _ulong) return Try(_ulong, Convert.ToInt32); + if (number is float _float) return Try(_float, Convert.ToInt32); + if (number is double _double) return Try(_double, Convert.ToInt32); + if (number is decimal _decimal) return Try(_decimal, Convert.ToInt32); + return GetNumber(number, Convert.ToInt32); + } /// 获取 UInt32 对象。 - public static uint UInt32(object number) => GetNumber(number, Convert.ToUInt32); + public static uint UInt32(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToUInt32); + if (number is byte _byte) return Try(_byte, Convert.ToUInt32); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToUInt32); + if (number is short _short) return Try(_short, Convert.ToUInt32); + if (number is ushort _ushort) return Try(_ushort, Convert.ToUInt32); + if (number is int _int) return Try(_int, Convert.ToUInt32); + if (number is uint _uint) return Try(_uint, Convert.ToUInt32); + if (number is long _long) return Try(_long, Convert.ToUInt32); + if (number is ulong _ulong) return Try(_ulong, Convert.ToUInt32); + if (number is float _float) return Try(_float, Convert.ToUInt32); + if (number is double _double) return Try(_double, Convert.ToUInt32); + if (number is decimal _decimal) return Try(_decimal, Convert.ToUInt32); + return GetNumber(number, Convert.ToUInt32); + } /// 获取 Int64 对象。 - public static long Int64(object number) => GetNumber(number, Convert.ToInt64); + public static long Int64(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToInt64); + if (number is byte _byte) return Try(_byte, Convert.ToInt64); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToInt64); + if (number is short _short) return Try(_short, Convert.ToInt64); + if (number is ushort _ushort) return Try(_ushort, Convert.ToInt64); + if (number is int _int) return Try(_int, Convert.ToInt64); + if (number is uint _uint) return Try(_uint, Convert.ToInt64); + if (number is long _long) return Try(_long, Convert.ToInt64); + if (number is ulong _ulong) return Try(_ulong, Convert.ToInt64); + if (number is float _float) return Try(_float, Convert.ToInt64); + if (number is double _double) return Try(_double, Convert.ToInt64); + if (number is decimal _decimal) return Try(_decimal, Convert.ToInt64); + return GetNumber(number, Convert.ToInt64); + } /// 获取 UInt64 对象。 - public static ulong UInt64(object number) => GetNumber(number, Convert.ToUInt64); + public static ulong UInt64(object number) + { + if (number is bool _bool) return Try(_bool, Convert.ToUInt64); + if (number is byte _byte) return Try(_byte, Convert.ToUInt64); + if (number is sbyte _sbyte) return Try(_sbyte, Convert.ToUInt64); + if (number is short _short) return Try(_short, Convert.ToUInt64); + if (number is ushort _ushort) return Try(_ushort, Convert.ToUInt64); + if (number is int _int) return Try(_int, Convert.ToUInt64); + if (number is uint _uint) return Try(_uint, Convert.ToUInt64); + if (number is long _long) return Try(_long, Convert.ToUInt64); + if (number is ulong _ulong) return Try(_ulong, Convert.ToUInt64); + if (number is float _float) return Try(_float, Convert.ToUInt64); + if (number is double _double) return Try(_double, Convert.ToUInt64); + if (number is decimal _decimal) return Try(_decimal, Convert.ToUInt64); + return GetNumber(number, Convert.ToUInt64); + } + + #endregion + + #region 转为字符串 + + /// 转为大写中文金额。 + public static string ToChinesePrice(decimal value) + { + if (value == 0M) return "零圆整"; + var s = value.ToString("#L#E#D#C#K#E#D#C#J#E#D#C#I#E#D#C#H#E#D#C#G#E#D#C#F#E#D#C#.0B0A"); + var d = Regex.Replace(s, @"((?<=-|^)[^1-9]*)|((?'z'0)[0A-E]*((?=[1-9])|(?'-z'(?=[F-L\.]|$))))|((?'b'[F-L])(?'z'0)[0A-L]*((?=[1-9])|(?'-z'(?=[\.]|$))))", "${b}${z}"); + var r = Regex.Replace(d, ".", m => "负元空零壹贰叁肆伍陆柒捌玖空空空空空空空分角拾佰仟万亿兆京垓秭穰"[m.Value[0] - '-'].ToString()); + if (!(r.Contains("分") || r.Contains("角"))) r = r + "整"; + if (value < 0M) r = "负" + r; + return r; + } #endregion diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 88a1175..088bfa5 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -488,6 +488,32 @@ namespace Apewer return true; } + /// 获取数组元素的类型。 + /// 参数必须是正确的数组类型。 + /// + /// + public static Type GetTypeOfArrayItem(Type arrayType) + { + if (arrayType == null) throw new ArgumentNullException(nameof(arrayType)); + if (!arrayType.BaseType.Equals(typeof(Array))) throw new ArgumentException($"参数 {arrayType} 不是有效的数组类型。"); + + var methods = arrayType.GetMethods(); + foreach (var method in methods) + { + if (method.Name.ToLower() != "set") continue; + + var parameters = method.GetParameters(); + if (parameters.Length != 2) continue; + + var length = parameters[0].ParameterType; + if (!length.Equals(typeof(int))) continue; + + var item = parameters[1].ParameterType; + return item; + } + throw new ArgumentException($"参数 {arrayType} 不是有效的数组类型。"); + } + #endregion #region Collect & Dispose @@ -648,6 +674,29 @@ namespace Apewer return memory; } +#if NETFRAMEWORK + + /// 将代码生成为程序集。 + public static Assembly Compile(string code, bool executable = false, bool inMemory = true) + { + using (var cdp = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("C#")) + { + var cp = new System.CodeDom.Compiler.CompilerParameters(); + cp.ReferencedAssemblies.Add(typeof(void).Assembly.Location); + cp.GenerateExecutable = false; + cp.GenerateInMemory = true; + cp.TempFiles = new System.CodeDom.Compiler.TempFileCollection(System.IO.Path.GetTempPath()); + + var cr = cdp.CompileAssemblyFromSource(cp, code); + if (cr.Errors.Count > 0) throw new Exception(cr.Errors[0].ErrorText); + + var assembly = cr.CompiledAssembly; + return assembly; + } + } + +#endif + #endregion #region Application @@ -847,20 +896,16 @@ namespace Apewer Logger.Internals.Info(typeof(RuntimeUtility), "Invoke " + type.FullName); RuntimeUtility.Title = type.FullName; - if (catchException) + try { - try - { - Activator.CreateInstance(type); - } - catch (Exception ex) - { - Logger.Internals.Exception(typeof(RuntimeUtility), ex); - } + Activator.CreateInstance(type); } - else + catch (Exception ex) { - Activator.CreateInstance(type); + var ex2 = ex.InnerException ?? ex; + Logger.Internals.Exception(typeof(RuntimeUtility), ex2); + + if (!catchException) throw ex2; } } RuntimeUtility.Title = Title; diff --git a/Apewer/Source/HttpRecord.cs b/Apewer/Source/HttpRecord.cs index 611ff09..3f9fcbf 100644 --- a/Apewer/Source/HttpRecord.cs +++ b/Apewer/Source/HttpRecord.cs @@ -7,7 +7,6 @@ namespace Apewer.Source { /// - [Table] [Serializable] public class HttpRecord : Record { diff --git a/Apewer/Source/IDbAdo.cs b/Apewer/Source/IDbAdo.cs index a1ab2e3..ebe8213 100644 --- a/Apewer/Source/IDbAdo.cs +++ b/Apewer/Source/IDbAdo.cs @@ -10,6 +10,10 @@ namespace Apewer.Source public interface IDbAdo : IDisposable { + /// 允许 ADO 抛出异常,取代返回错误信息。 + /// FALSE(默认值) + bool ThrowAdoException { get; set; } + #region Connection /// 获取连接。 diff --git a/Apewer/Source/Query.cs b/Apewer/Source/Query.cs index 43bff5a..3db51bf 100644 --- a/Apewer/Source/Query.cs +++ b/Apewer/Source/Query.cs @@ -92,134 +92,30 @@ namespace Apewer.Source #region Value /// 获取默认表中第 0 行、第 0 列的单元格内容。 - public object Value() - { - if (_disposed) return null; - return Value(0, 0); - } + public object Value() => Table.Value(0, 0); /// 获取默认表中指定行中第 0 列的内容。 /// 行索引,从 0 开始。 - public object Value(int rowIndex) - { - if (_disposed) return null; - return Value(rowIndex, 0); - } + public object Value(int rowIndex) => Table.Value(rowIndex, 0); /// 获取默认表中第 0 行指定列的内容。 /// 列名称/字段名称,此名称不区分大小写。 - public object Value(string columnName) - { - if (_disposed) return null; - return Value(0, columnName); - } + public object Value(string columnName) => Table.Value(0, columnName); /// 获取默认表中指定单元格的内容。 /// 行索引,从 0 开始。 /// 列索引,从 0 开始。 - public object Value(int rowIndex, int columnIndex) - { - if (_disposed) return null; - if (_table != null) - { - if (rowIndex >= 0 && rowIndex < _table.Rows.Count) - { - if (columnIndex >= 0 && columnIndex < _table.Columns.Count) - { - var value = _table.Rows[rowIndex][columnIndex]; - if (value == null || value.Equals(DBNull.Value)) return null; - return value; - } - } - } - return null; - } + public object Value(int rowIndex, int columnIndex) => Table.Value(rowIndex, columnIndex); /// 获取默认表中指定单元的内容。 /// 行索引,从 0 开始。 /// 列名称/字段名称,此名称不区分大小写。 - public object Value(int rowIndex, string columnName) - { - if (_disposed) return null; - if ((Table != null) && !string.IsNullOrEmpty(columnName)) - { - if ((rowIndex < Table.Rows.Count) && (rowIndex >= 0)) - { - try - { - var value = Table.Rows[rowIndex][columnName]; - if (value == null || value.Equals(DBNull.Value)) return null; - return value; - } - catch { } - } - } - return null; - } - - /// 搜索默认表。 - /// 搜索条件:列名。 - /// 搜索条件:列值。 - /// 搜索结果。 - private object Value(string conditionColumn, string conditionValue, string resultColumn) - { - if (_disposed) return null; - if ((Table != null) && (!string.IsNullOrEmpty(conditionColumn)) && (conditionValue != null) && (!string.IsNullOrEmpty(resultColumn))) - { - for (int i = 0; i < Table.Rows.Count; i++) - { - try - { - var cValue = Table.Rows[i][conditionColumn]; - if (cValue == null || cValue.Equals(DBNull.Value)) cValue = null; - if (cValue.ToString() == conditionValue) return Table.Rows[i][resultColumn]; - } - catch { } - } - } - return null; - } - - /// 搜索默认表。 - /// 搜索条件:列名。 - /// 搜索条件:列值。 - /// 搜索结果的列名。 - private object Value(int conditionColumn, string conditionValue, int resultColumn) - { - if (_disposed) return null; - if ((Table != null) && (conditionColumn >= 0) && (conditionValue != null) && (resultColumn >= 0)) - { - if ((conditionColumn < Table.Columns.Count) && (resultColumn < Table.Columns.Count)) - { - for (int i = 0; i < Table.Rows.Count; i++) - { - try - { - var cValue = Table.Rows[i][conditionColumn]; - if (cValue == null || cValue.Equals(DBNull.Value)) cValue = null; - if (cValue.ToString() == conditionValue) return Table.Rows[i][resultColumn]; - } - catch { } - } - } - } - return null; - } + public object Value(int rowIndex, string columnName) => Table.Value(rowIndex, columnName); #endregion #region Method - /// 搜索默认表。 - /// 搜索条件:列名。 - /// 搜索条件:列值。 - /// 搜索结果。 - private string Text(int conditionColumn, string conditionValue, int resultColumn) - { - var value = Value(conditionColumn, conditionValue, resultColumn); - return Text(value); - } - /// 释放系统资源。 public void Dispose() { @@ -236,28 +132,6 @@ namespace Apewer.Source // GC.SuppressFinalize(this); } - /// 当不指定格式化程序时,自动根据类型选择预置的格式化程序。 - private static Func GetValueFormatter() - { - var type = typeof(T); - if (type.Equals(typeof(string))) return TextFormatter; - else return ForceFormatter; - } - - /// 获取指定列的所有值,无效值不加入结果。 - public T[] ReadColumn(int column = 0, Func formatter = null) => SourceUtility.Column(this, (r) => (formatter ?? GetValueFormatter()).Invoke(Value(r, column))); - - /// 获取指定列的所有值,无效值不加入结果。 - /// - public T[] ReadColumn(string column, Func formatter = null) => SourceUtility.Column(this, (r) => (formatter ?? GetValueFormatter()).Invoke(Value(r, column))); - - /// 获取指定列的所有值,无效值不加入结果。 - public string[] ReadColumn(int column = 0) => SourceUtility.Column(this, (r) => this.Text(r, column)); - - /// 获取指定列的所有值,无效值不加入结果。 - /// - public string[] ReadColumn(string column) => SourceUtility.Column(this, (r) => this.Text(r, column)); - #endregion #region 模型化、IToJson @@ -265,52 +139,17 @@ namespace Apewer.Source /// 转换为 Json 对象。 public Json ToJson() { - var columns = Json.NewArray(); - var rows = Json.NewArray(); - - var table = _table; - if (!_disposed && table != null) - { - var columnsCount = table.Columns.Count; - for (var c = 0; c < columnsCount; c++) - { - var dc = table.Columns[c]; - var column = Json.NewObject(); - column.SetProperty("name", dc.ColumnName); - column.SetProperty("type", dc.DataType.FullName); - columns.AddItem(column); - } - - var rowsCount = table.Rows.Count; - for (var r = 0; r < rowsCount; r++) - { - var row = Json.NewArray(); - for (var c = 0; c < columnsCount; c++) - { - var v = Value(r, c); - if (v == null) row.AddItem(); - else if (v.Equals(DBNull.Value)) row.AddItem(); - else if (v is byte vByte) row.AddItem(vByte); - else if (v is short vInt16) row.AddItem(vInt16); - else if (v is int vInt32) row.AddItem(vInt32); - else if (v is long vInt64) row.AddItem(vInt64); - else if (v is float vSingle) row.AddItem(vSingle); - else if (v is double vDouble) row.AddItem(vDouble); - else if (v is decimal vDecimal) row.AddItem(vDecimal); - else if (v is bool vBoolean) row.AddItem(vBoolean); - else if (v is byte[] vBytes) row.AddItem(vBytes.Base64()); - else if (v is DateTime vDateTime) row.AddItem(vDateTime.Lucid()); - else row.AddItem(v.ToString()); - } - rows.AddItem(row); - } - } - var jsonObject = Json.NewObject(); jsonObject.SetProperty("success", _success); jsonObject.SetProperty("message", _message); - jsonObject.SetProperty("columns", columns); - jsonObject.SetProperty("rows", rows); + if (Table != null) + { + var columns = SourceUtility.ToJson(Table.Columns); + jsonObject.SetProperty("columns", columns); + + var rows = SourceUtility.ToJson(Table.Rows); + jsonObject.SetProperty("rows", rows); + } return jsonObject; } @@ -364,36 +203,6 @@ namespace Apewer.Source #endregion - #region Static - - private static T ForceFormatter(object input) => (T)input; - - private static T TextFormatter(object input) => (T)(Text(input) as object); - - private static ObjectDisposedException DisposedException { get { return new ObjectDisposedException(typeof(Query).FullName); } } - - #endregion - - #region Extension - - internal static string Text(object value) - { - if (value == null) return null; - if (value is string str) return str; - try { return value.ToString() ?? ""; } catch { } - return ""; - } - - internal static Class DateTime(object value) - { - if (value is DateTime dt) return dt; - DateTime result; - var parsed = System.DateTime.TryParse(value.ToString(), out result); - return parsed ? new Class(result) : null; - } - - #endregion - } } diff --git a/Apewer/Source/SourceUtility.cs b/Apewer/Source/SourceUtility.cs index 2b4d122..bacc1fc 100644 --- a/Apewer/Source/SourceUtility.cs +++ b/Apewer/Source/SourceUtility.cs @@ -4,8 +4,6 @@ using System.Data; using System.Reflection; using System.Text; -using static Apewer.NumberUtility; - namespace Apewer.Source { @@ -13,7 +11,7 @@ namespace Apewer.Source public static class SourceUtility { - #region IQuery -> IRecord + #region ORM /// 读取所有行,生成列表。 public static T[] Fill(this IQuery query) where T : class, new() @@ -35,41 +33,19 @@ namespace Apewer.Source return Fill(query.Table, model); } - /// 获取指定列的所有值,无效值不加入结果。 - public static T[] Column(this IQuery query, Func filler) - { - if (query == null || filler == null) return new T[0]; - - var rows = query.Rows; - var output = new T[rows]; - var added = 0; - for (int r = 0; r < rows; r++) - { - var value = filler(r); - if (value == null) continue; - if (value is string str) - { - if (str == "") continue; - } - output[added] = value; - added++; - } - - if (added < 1) return new T[0]; - if (added == rows) return output; - var output2 = new T[added]; - Array.Copy(output, output2, added); - return output2; - } + /// 将 Query 的行,填充到模型实体。 + /// 填充失败时返回 NULL 值。 + /// + public static object FillRow(IQuery query, int rowIndex, Type model, TableStructure structure) => FillRow(query?.Table, rowIndex, model, structure); /// 将 Query 的行,填充到模型实体。 /// 填充失败时返回 NULL 值。 /// - public static object Row(IQuery query, int rowIndex, Type model, TableStructure structure) + public static object FillRow(DataTable table, int rowIndex, Type model, TableStructure structure) { // 检查参数。 - if (query == null || model == null || structure == null) return null; - if (rowIndex < 0 || rowIndex >= query.Rows) return null; + if (table == null || model == null || structure == null) return null; + if (rowIndex < 0 || rowIndex >= table.Rows.Count) return null; if (!RuntimeUtility.CanNew(model)) return null; // 变量别名。 @@ -99,7 +75,9 @@ namespace Apewer.Source field = property.Name; } - var value = query.Value(r, field); + + var value = table.Rows[r][field]; + if (value != null && value.Equals(DBNull.Value)) value = null; var setted = Set(record, property, value); } return record; @@ -122,33 +100,33 @@ namespace Apewer.Source else if (pt.Equals(typeof(string))) setter.Invoke(record, new object[] { value.ToString() }); else if (pt.Equals(typeof(DateTime))) setter.Invoke(record, new object[] { value }); - else if (pt.Equals(typeof(bool))) setter.Invoke(record, new object[] { Boolean(value) }); - else if (pt.Equals(typeof(byte))) setter.Invoke(record, new object[] { Byte(value) }); - else if (pt.Equals(typeof(sbyte))) setter.Invoke(record, new object[] { SByte(value) }); - else if (pt.Equals(typeof(short))) setter.Invoke(record, new object[] { Int16(value) }); - else if (pt.Equals(typeof(ushort))) setter.Invoke(record, new object[] { UInt16(value) }); - else if (pt.Equals(typeof(int))) setter.Invoke(record, new object[] { Int32(value) }); - else if (pt.Equals(typeof(uint))) setter.Invoke(record, new object[] { UInt32(value) }); - else if (pt.Equals(typeof(long))) setter.Invoke(record, new object[] { Int64(value) }); - else if (pt.Equals(typeof(ulong))) setter.Invoke(record, new object[] { UInt64(value) }); - else if (pt.Equals(typeof(float))) setter.Invoke(record, new object[] { Single(value) }); - else if (pt.Equals(typeof(double))) setter.Invoke(record, new object[] { Double(value) }); - else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { Decimal(value) }); + else if (pt.Equals(typeof(bool))) setter.Invoke(record, new object[] { NumberUtility.Boolean(value) }); + else if (pt.Equals(typeof(byte))) setter.Invoke(record, new object[] { NumberUtility.Byte(value) }); + else if (pt.Equals(typeof(sbyte))) setter.Invoke(record, new object[] { NumberUtility.SByte(value) }); + else if (pt.Equals(typeof(short))) setter.Invoke(record, new object[] { NumberUtility.Int16(value) }); + else if (pt.Equals(typeof(ushort))) setter.Invoke(record, new object[] { NumberUtility.UInt16(value) }); + else if (pt.Equals(typeof(int))) setter.Invoke(record, new object[] { NumberUtility.Int32(value) }); + else if (pt.Equals(typeof(uint))) setter.Invoke(record, new object[] { NumberUtility.UInt32(value) }); + else if (pt.Equals(typeof(long))) setter.Invoke(record, new object[] { NumberUtility.Int64(value) }); + else if (pt.Equals(typeof(ulong))) setter.Invoke(record, new object[] { NumberUtility.UInt64(value) }); + else if (pt.Equals(typeof(float))) setter.Invoke(record, new object[] { NumberUtility.Single(value) }); + else if (pt.Equals(typeof(double))) setter.Invoke(record, new object[] { NumberUtility.Double(value) }); + else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { NumberUtility.Decimal(value) }); #if !NET20 else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable((DateTime)value) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Boolean(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Byte(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(SByte(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int16(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt16(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int32(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt32(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Int64(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(UInt64(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Single(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Double(value)) }); - else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(Decimal(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Boolean(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Byte(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.SByte(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Int16(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.UInt16(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Int32(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.UInt32(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Int64(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.UInt64(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Single(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Double(value)) }); + else if (pt.Equals(typeof(Nullable))) setter.Invoke(record, new object[] { new Nullable(NumberUtility.Decimal(value)) }); #endif else @@ -163,6 +141,176 @@ namespace Apewer.Source return false; } + /// 解析 DataTable,填充没行到到指定的类型中,形成数组。 + /// 将要读取的表。 + /// 当类型不同时,尝试转换以兼容。 + /// 由指定类型组成的数组。 + /// + /// + public static T[] Fill(this DataTable table, bool compatible = true) + { + if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。"); + var objects = Fill(table, typeof(T), compatible); + var count = objects.Length; + var array = new T[count]; + for (var i = 0; i < count; i++) array[i] = (T)objects[i]; + return array; + } + + /// 解析 DataTable,填充没行到到指定的类型中,形成数组。 + /// 将要读取的表。 + /// 要填充的目标类型,必须是可实例化的引用类型。 + /// 当类型不同时,尝试转换以兼容。 + /// 由指定类型组成的数组。 + /// + /// + public static object[] Fill(this DataTable table, Type model, bool compatible = true) + { + if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。"); + if (model == null) throw new ArgumentNullException(nameof(model), $"参数 {model} 无效。"); + + // 检查模型是否允许填充。 + var ts = TableStructure.Parse(model, true, true); + if (ts == null) throw new ArgumentException($"无法填充到类型 {model.FullName} 中。"); + + // 检查行数。 + var rows = table.Rows; + var rowsCount = rows.Count; + if (rowsCount < 1) return new object[0]; + + // 确定数组。 + var array = new object[rowsCount]; + for (var i = 0; i < rowsCount; i++) array[i] = Activator.CreateInstance(model, true); + + // 检查列数。 + var columns = table.Columns; + var columnsCount = columns.Count; + if (columnsCount < 1) return array; + + // 解析表头,仅保留有名称的列。 + var sc = 0; + var sfs = new string[columnsCount]; + var sts = new Type[columnsCount]; + var sis = new int[columnsCount]; + for (var i = 0; i < columnsCount; i++) + { + var column = columns[i]; + var key = column.ColumnName.Lower(); + if (string.IsNullOrEmpty(key)) continue; + if (sfs.Contains(key)) continue; + sfs[sc] = key; + sts[sc] = column.DataType; + sis[sc] = i; + sc++; + } + if (sc < 1) return array; + + // 解析模型列。 + var cas = ts.Fillable; + var dc = 0; + var dfs = new string[cas.Length]; + var dts = new ColumnAttribute[cas.Length]; + for (var i = 0; i < cas.Length; i++) + { + var ca = cas[i]; + var key = ca.Field.Lower(); + if (string.IsNullOrEmpty(key)) continue; + if (dfs.Contains(key)) continue; + dfs[dc] = key; + dts[dc] = ca; + dc++; + } + if (dc < 1) return array; + + // 遍历、填充。 + for (var r = 0; r < rowsCount; r++) + { + var record = array[r]; + + // 遍历 table 的列。 + for (var s = 0; s < sc; s++) + { + var sf = sfs[s]; + + // 遍历 model 的列。 + for (var d = 0; d < dc; d++) + { + var df = dfs[d]; + if (df != sf) continue; + + // 取值、填充。 + var value = rows[r][sis[s]]; + Fill(record, dts[d], sts[s], value, compatible); + break; + } + } + } + + return array; + } + + static bool Fill(object record, ColumnAttribute ca, Type st, object value, bool compatible) + { + // 如果是 NULL 则忽略填充。 + if (value.IsNull()) return false; + + // 获取属性的类型,必须与 table 中的类型相同。 + var prop = ca.Property; + if (prop.PropertyType == st) + { + prop.SetValue(record, value, null); + return true; + } + + // 类型不同且不需要兼容时,不填充。 + if (!compatible) return false; + + // 根据属性类型设置值。 + var pt = prop.PropertyType; + if (pt.Equals(typeof(object))) prop.SetValue(record, value, null); + else if (pt.Equals(typeof(byte[]))) prop.SetValue(record, (byte[])value, null); + else if (pt.Equals(typeof(string))) prop.SetValue(record, value.ToString(), null); + + else if (pt.Equals(typeof(DateTime))) prop.SetValue(record, value, null); + else if (pt.Equals(typeof(bool))) prop.SetValue(record, NumberUtility.Boolean(value), null); + else if (pt.Equals(typeof(byte))) prop.SetValue(record, NumberUtility.Byte(value), null); + else if (pt.Equals(typeof(sbyte))) prop.SetValue(record, NumberUtility.SByte(value), null); + else if (pt.Equals(typeof(short))) prop.SetValue(record, NumberUtility.Int16(value), null); + else if (pt.Equals(typeof(ushort))) prop.SetValue(record, NumberUtility.UInt16(value), null); + else if (pt.Equals(typeof(int))) prop.SetValue(record, NumberUtility.Int32(value), null); + else if (pt.Equals(typeof(uint))) prop.SetValue(record, NumberUtility.UInt32(value), null); + else if (pt.Equals(typeof(long))) prop.SetValue(record, NumberUtility.Int64(value), null); + else if (pt.Equals(typeof(ulong))) prop.SetValue(record, NumberUtility.UInt64(value), null); + else if (pt.Equals(typeof(float))) prop.SetValue(record, NumberUtility.Single(value), null); + else if (pt.Equals(typeof(double))) prop.SetValue(record, NumberUtility.Double(value), null); + else if (pt.Equals(typeof(decimal))) prop.SetValue(record, NumberUtility.Decimal(value), null); + + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable((DateTime)value), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Boolean(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Byte(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.SByte(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Int16(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.UInt16(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Int32(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.UInt32(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Int64(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.UInt64(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Single(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Double(value)), null); + else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(NumberUtility.Decimal(value)), null); + + else + { + try + { + prop.SetValue(record, value, null); + return true; + } + catch { } + } + return false; + } + #endregion #region Record @@ -493,177 +641,7 @@ namespace Apewer.Source #endregion - #region DataTable - - /// 解析 DataTable,填充没行到到指定的类型中,形成数组。 - /// 将要读取的表。 - /// 当类型不同时,尝试转换以兼容。 - /// 由指定类型组成的数组。 - /// - /// - public static T[] Fill(this DataTable table, bool compatible = true) - { - if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。"); - var objects = Fill(table, typeof(T), compatible); - var count = objects.Length; - var array = new T[count]; - for (var i = 0; i < count; i++) array[i] = (T)objects[i]; - return array; - } - - /// 解析 DataTable,填充没行到到指定的类型中,形成数组。 - /// 将要读取的表。 - /// 要填充的目标类型,必须是可实例化的引用类型。 - /// 当类型不同时,尝试转换以兼容。 - /// 由指定类型组成的数组。 - /// - /// - public static object[] Fill(this DataTable table, Type model, bool compatible = true) - { - if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。"); - if (model == null) throw new ArgumentNullException(nameof(model), $"参数 {model} 无效。"); - - // 检查模型是否允许填充。 - var ts = TableStructure.Parse(model, true, true); - if (ts == null) throw new ArgumentException($"无法填充到类型 {model.FullName} 中。"); - - // 检查行数。 - var rows = table.Rows; - var rowsCount = rows.Count; - if (rowsCount < 1) return new object[0]; - - // 确定数组。 - var array = new object[rowsCount]; - for (var i = 0; i < rowsCount; i++) array[i] = Activator.CreateInstance(model, true); - - // 检查列数。 - var columns = table.Columns; - var columnsCount = columns.Count; - if (columnsCount < 1) return array; - - // 解析表头,仅保留有名称的列。 - var sc = 0; - var sfs = new string[columnsCount]; - var sts = new Type[columnsCount]; - var sis = new int[columnsCount]; - for (var i = 0; i < columnsCount; i++) - { - var column = columns[i]; - var key = column.ColumnName.Lower(); - if (string.IsNullOrEmpty(key)) continue; - if (sfs.Contains(key)) continue; - sfs[sc] = key; - sts[sc] = column.DataType; - sis[sc] = i; - sc++; - } - if (sc < 1) return array; - - // 解析模型列。 - var cas = ts.Fillable; - var dc = 0; - var dfs = new string[cas.Length]; - var dts = new ColumnAttribute[cas.Length]; - for (var i = 0; i < cas.Length; i++) - { - var ca = cas[i]; - var key = ca.Field.Lower(); - if (string.IsNullOrEmpty(key)) continue; - if (dfs.Contains(key)) continue; - dfs[dc] = key; - dts[dc] = ca; - dc++; - } - if (dc < 1) return array; - - // 遍历、填充。 - for (var r = 0; r < rowsCount; r++) - { - var record = array[r]; - - // 遍历 table 的列。 - for (var s = 0; s < sc; s++) - { - var sf = sfs[s]; - - // 遍历 model 的列。 - for (var d = 0; d < dc; d++) - { - var df = dfs[d]; - if (df != sf) continue; - - // 取值、填充。 - var value = rows[r][sis[s]]; - Fill(record, dts[d], sts[s], value, compatible); - break; - } - } - } - - return array; - } - - static bool Fill(object record, ColumnAttribute ca, Type st, object value, bool compatible) - { - // 如果是 NULL 则忽略填充。 - if (value.IsNull()) return false; - - // 获取属性的类型,必须与 table 中的类型相同。 - var prop = ca.Property; - if (prop.PropertyType == st) - { - prop.SetValue(record, value, null); - return true; - } - - // 类型不同且不需要兼容时,不填充。 - if (!compatible) return false; - - // 根据属性类型设置值。 - var pt = prop.PropertyType; - if (pt.Equals(typeof(object))) prop.SetValue(record, value, null); - else if (pt.Equals(typeof(byte[]))) prop.SetValue(record, (byte[])value, null); - else if (pt.Equals(typeof(string))) prop.SetValue(record, value.ToString(), null); - - else if (pt.Equals(typeof(DateTime))) prop.SetValue(record, value, null); - else if (pt.Equals(typeof(bool))) prop.SetValue(record, Boolean(value), null); - else if (pt.Equals(typeof(byte))) prop.SetValue(record, Byte(value), null); - else if (pt.Equals(typeof(sbyte))) prop.SetValue(record, SByte(value), null); - else if (pt.Equals(typeof(short))) prop.SetValue(record, Int16(value), null); - else if (pt.Equals(typeof(ushort))) prop.SetValue(record, UInt16(value), null); - else if (pt.Equals(typeof(int))) prop.SetValue(record, Int32(value), null); - else if (pt.Equals(typeof(uint))) prop.SetValue(record, UInt32(value), null); - else if (pt.Equals(typeof(long))) prop.SetValue(record, Int64(value), null); - else if (pt.Equals(typeof(ulong))) prop.SetValue(record, UInt64(value), null); - else if (pt.Equals(typeof(float))) prop.SetValue(record, Single(value), null); - else if (pt.Equals(typeof(double))) prop.SetValue(record, Double(value), null); - else if (pt.Equals(typeof(decimal))) prop.SetValue(record, Decimal(value), null); - - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable((DateTime)value), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Boolean(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Byte(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(SByte(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Int16(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(UInt16(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Int32(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(UInt32(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Int64(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(UInt64(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Single(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Double(value)), null); - else if (pt.Equals(typeof(Nullable))) prop.SetValue(record, new Nullable(Decimal(value)), null); - - else - { - try - { - prop.SetValue(record, value, null); - return true; - } - catch { } - } - return false; - } + #region 数据模型 -> DataTable /// 将多个实体元素转换为 DataTable。 /// 实体元素的类型。 @@ -748,9 +726,13 @@ namespace Apewer.Source return table; } + #endregion + + #region DataTable 序列化 + /// 转换 数组,每行记录为一个 ObjectSet 对象。 /// 当参数 table 无效时返回 0 长度的 数组。 - internal static ObjectSet[] ObjectSet(this DataTable table) + public static ObjectSet[] ObjectSet(this DataTable table) { if (table == null) return new ObjectSet[0]; @@ -780,7 +762,104 @@ namespace Apewer.Source return oss; } - internal static string Csv(DataTable table, bool withHead) + /// 转换为 Json 对象。 + public static Json ToJson(this DataTable table, Func dateTimeFormatter = null) + { + if (table == null) return null; + + var columns = ToJson(table.Columns); + var rows = ToJson(table.Rows); + + var jsonObject = Json.NewObject(); + jsonObject.SetProperty("columns", columns); + jsonObject.SetProperty("rows", rows); + return jsonObject; + } + + /// 转换为 Json 对象。 + public static Json ToJson(this DataColumnCollection columns) + { + if (columns == null) return null; + + var json = Json.NewArray(); + var count = columns.Count; + for (var c = 0; c < count; c++) + { + var dc = columns[c]; + var column = Json.NewObject(); + column.SetProperty("name", dc.ColumnName); + column.SetProperty("type", dc.DataType.FullName); + json.AddItem(column); + } + return json; + } + + /// 转换为 Json 对象。 + public static Json ToJson(this DataRowCollection rows, Func dateTimeFormatter = null) + { + if (rows == null) return null; + + var json = Json.NewArray(); + var count = rows.Count; + for (var r = 0; r < count; r++) + { + json.AddItem(ToJson(rows[r], dateTimeFormatter)); + } + return json; + } + + /// 转换为 Json 对象。 + public static Json ToJson(this DataRow row, Func dateTimeFormatter = null) + { + if (row == null) return null; + var cells = row.ItemArray; + var count = cells.Length; + + var json = Json.NewArray(); + for (var c = 0; c < count; c++) + { + var value = cells[c]; + if (value == null || value.Equals(DBNull.Value)) + { + json.AddItem(); + continue; + } + + if (value is DateTime vDateTime) + { + if (dateTimeFormatter == null) + { + json.AddItem(Json.SerializeDateTime(vDateTime)); + continue; + } + else + { + value = dateTimeFormatter.Invoke(vDateTime); + if (value == null || value.Equals(DBNull.Value)) + { + json.AddItem(); + continue; + } + } + } + + if (value is string @string) json.AddItem(@string); + else if (value is byte @byte) json.AddItem(@byte); + else if (value is short @short) json.AddItem(@short); + else if (value is int @int) json.AddItem(@int); + else if (value is long @long) json.AddItem(@long); + else if (value is float @float) json.AddItem(@float); + else if (value is double @double) json.AddItem(@double); + else if (value is decimal @decimal) json.AddItem(@decimal); + else if (value is bool @bool) json.AddItem(@bool); + else if (value is byte[] bytes) json.AddItem(bytes.Base64()); + else json.AddItem(TextUtility.Text(value)); + } + return json; + } + + /// 转换 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。 + public static string Csv(DataTable table, bool withHead = false) { if (table == null) return null; @@ -864,6 +943,123 @@ namespace Apewer.Source #endregion + #region DataTable 快捷操作 + + /// 获取默认表中指定单元格的内容。 + /// 数据表。 + /// 行索引,从 0 开始。 + /// 列索引,从 0 开始。 + public static object Value(this DataTable table, int rowIndex, int columnIndex) + { + if (table != null) + { + if (rowIndex >= 0 && rowIndex < table.Rows.Count) + { + if (columnIndex >= 0 && columnIndex < table.Columns.Count) + { + var value = table.Rows[rowIndex][columnIndex]; + if (value == null || value.Equals(DBNull.Value)) return null; + return value; + } + } + } + return null; + } + + /// 获取默认表中指定单元的内容。 + /// 数据表。 + /// 行索引,从 0 开始。 + /// 列名称/字段名称,此名称不区分大小写。 + public static object Value(this DataTable table, int rowIndex, string columnName) + { + if (table != null && !string.IsNullOrEmpty(columnName)) + { + if ((rowIndex < table.Rows.Count) && (rowIndex >= 0)) + { + try + { + var value = table.Rows[rowIndex][columnName]; + if (value == null || value.Equals(DBNull.Value)) return null; + return value; + } + catch { } + } + } + return null; + } + + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Class DateTime(this DataTable table, int row = 0, int column = 0) => table == null ? null : ClockUtility.DateTime(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 + public static Class DateTime(this DataTable table, int row, string column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Int32 Int32(this DataTable table, int row = 0, int column = 0) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 + public static Int32 Int32(this DataTable table, int row, string column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Int64 Int64(this DataTable table, int row = 0, int column = 0) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 + public static Int64 Int64(this DataTable table, int row, string column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static Decimal Decimal(this DataTable table, int row = 0, int column = 0) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 + public static Decimal Decimal(this DataTable table, int row, string column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。> + public static Double Double(this DataTable table, int row = 0, int column = 0) => table == null ? 0D : NumberUtility.Double(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。> + public static Double Double(this DataTable table, int row, string column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 + public static string Text(this DataTable table, int row = 0, int column = 0) => table == null ? null : TextUtility.Text(table.Value(row, column)); + + /// 获取默认表中指定单元格的内容。从第 0 行开始。 + public static string Text(this DataTable table, int row, string column) => table == null ? null : TextUtility.Text(table.Value(row, column)); + + #endregion + + #region Dynamic + +#if NET40_OR_GREATER + + /// 转换 ObjectSet 数组为 dynamic 数组。 + public static dynamic[] Dynamic(this ObjectSet[] oss) + { + if (oss == null) return new dynamic[0]; + + var eos = oss.Expando(); + var ds = new dynamic[eos.Length]; + eos.CopyTo(ds, 0); + return ds; + } + +#endif + + #endregion + + #region 表达式计算 + + /// 计算文本表达式。 + public static object Compute(string expression) + { + using (var table = new DataTable()) + { + var result = table.Compute(expression, null); + if (result.IsNull()) return null; + return result; + } + } + + #endregion + } } diff --git a/Apewer/StorageUtility.cs b/Apewer/StorageUtility.cs index 9253b3c..cf52825 100644 --- a/Apewer/StorageUtility.cs +++ b/Apewer/StorageUtility.cs @@ -23,6 +23,16 @@ namespace Apewer #region NTFS 流 + /// 搜索文件是否在 NTFS 流中附带了锁定标记。 + /// 此方法仅支持 Windows 系统。 + public static bool HasZoneIdentifier(string path) + { + var info = new FileInfo(path); + var streamPath = info.FullName + NtfsUnlocker.Postfix; + var streamExists = NtfsUnlocker.FileExists(streamPath); + return streamExists; + } + /// 解锁下载的文件。 /// 此方法仅支持 Windows 系统。 public static string DeleteZoneIdentifier(string path) => NtfsUnlocker.DeleteZoneIdentifier(path); @@ -327,6 +337,25 @@ namespace Apewer return null; } + /// 确保指定路径存在文件,若不存在则新建。 + /// 文件路径。 + public static bool AssureFile(string path) + { + if (string.IsNullOrEmpty(path)) return false; + if (File.Exists(path)) return true; + if (Directory.Exists(path)) return false; + if (!AssureParent(path)) return false; + try + { + File.Create(path).Dispose(); + return true; + } + catch + { + return false; + } + } + /// 创建一个空文件且不保留句柄。 /// 文件路径,若已存在则返回失败。 /// 文件长度(字节数)。 diff --git a/Apewer/TextUtility.cs b/Apewer/TextUtility.cs index 2d3903a..4133849 100644 --- a/Apewer/TextUtility.cs +++ b/Apewer/TextUtility.cs @@ -9,7 +9,7 @@ namespace Apewer { /// 文本实用工具。 - public class TextUtility + public static class TextUtility { const string BlankChars = "  \n\r\t\f\b\a"; // 在 IsBlank 和 Trim 中视为空白的字符。 @@ -36,6 +36,27 @@ namespace Apewer /// 空文本。 public const string Empty = ""; + /// 返回表示指定对象的字符串。 + public static string Text(object value) + { + if (value is string str) return str; + if (value == null) return null; + if (value.Equals(DBNull.Value)) return null; + + if (value is Type t) return t.Name; + if (value is char[] chars) return new string(chars); + + var type = value.GetType(); + var toString = type.GetMethod(nameof(object.ToString), Type.EmptyTypes); + if (toString.DeclaringType.Equals(type)) + { + try { return value.ToString(); } + catch { return null; } + } + + return "<" + type.Name + ">"; + } + /// 字符串为空。 public static bool IsEmpty(string text) => text == null || text == Empty; @@ -111,10 +132,51 @@ namespace Apewer } /// 合并为字符串。 - public static string Merge(params object[] cells) => PrivateJoin(null, CollectionUtility.ParseParams(cells)); + public static string Merge(params object[] cells) => Join(null, cells); /// 合并为字符串。 - public static string Join(string separator, params object[] cells) => PrivateJoin(separator, CollectionUtility.ParseParams(cells)); + public static string Join(string separator, params object[] cells) + { + if (cells == null) return Empty; + + while (cells.Length == 1) + { + var first = cells[0]; + if (first.IsNull()) return Empty; + if (first is char[] chars) return new string(chars); + if (first is object[] objects) + { + cells = objects; + continue; + } + break; + } + + { + var sb = new StringBuilder(); + var first = true; + var hasSeparator = !string.IsNullOrEmpty(separator); + foreach (var cell in cells) + { + if (cell.IsNull()) continue; + + var text = null as string; + if (cell is string str) text = str; + else if (cell is char[] chars) text = new string(chars); + else text = Text(cell); + + if (string.IsNullOrEmpty(text)) continue; + if (hasSeparator) + { + if (first) first = false; + else sb.Append(separator); + } + sb.Append(text); + } + var result = sb.ToString(); + return result; + } + } /// 重复指定字符,直到达到指定长度。 /// 要重复的字符。 @@ -479,6 +541,21 @@ namespace Apewer return text.Substring(starts, length - starts - ends); } + /// 修剪字符串数组,修剪元素,并去除空字符串。 + public static string[] Trim(this IEnumerable strings) + { + var ab = new ArrayBuilder(); + if (strings == null) return ab.Export(); + foreach (var str in strings) + { + var trim = Trim(str); + if (string.IsNullOrEmpty(trim)) continue; + ab.Add(trim); + } + var array = ab.Export(); + return array; + } + /// 剪取文本内容,若指定头部为空则从原文本首部起,若指定尾部为空则至原文本末尾。 /// 剪取后的内容,不包含 head 和 foot。 public static string Cut(string text, string head = null, string foot = null) diff --git a/Apewer/Web/ApiContext.cs b/Apewer/Web/ApiContext.cs index b0806d8..86ff90e 100644 --- a/Apewer/Web/ApiContext.cs +++ b/Apewer/Web/ApiContext.cs @@ -7,46 +7,65 @@ using System.Text; namespace Apewer.Web { - internal sealed class ApiContext + /// API 上下文。 + public sealed class ApiContext { - internal ApiInvoker _invoker = null; - internal ApiEntries _entries = null; - internal ApiEntry _entry = null; - internal ApiProvider _provider = null; - internal Action _catcher = null; + #region 构造参数 - internal ApiOptions _options = null; - internal Stopwatch _stopwatch = null; + private ApiInvoker _invoker = null; + private ApiProvider _provider = null; + private ApiEntries _entries = null; - internal Uri _url = null; - internal HttpMethod _method = HttpMethod.NULL; - internal ApiRequest _request = null; - internal ApiResponse _ersponse = null; + private DateTime _beginning = DateTime.Now; + private ApiOptions _options = null; + /// 此上下文启动的时间。 + public DateTime Beginning { get => _beginning; } + + /// API 调用器。 public ApiInvoker Invoker { get => _invoker; } + /// API 提供程序。 + public ApiProvider Provider { get => _provider; } + + /// API 入口集。 public ApiEntries Entries { get => _entries; } - public ApiEntry Entry { get => _entry; } + /// API 选项。 + public ApiOptions Options { get => _options; } + + #endregion - public ApiProvider Provider { get => _provider; } + #region 执行过程中产生的内容 - public Action Catcher { get => _catcher; } + /// API 入口。 + public ApiEntry Entry { get; internal set; } - public ApiOptions Options { get => _options; } + /// API 请求。 + public ApiRequest Request { get; internal set; } + + /// API 响应。 + public ApiResponse Response { get; internal set; } - public Stopwatch Stopwatch { get => _stopwatch; } + /// API 控制器实例。 + public ApiController Controller { get; internal set; } - public Uri Url { get => _url; } + #endregion - public HttpMethod Method { get => _method; } + internal ApiContext(ApiInvoker invoker, ApiProvider provider, ApiEntries entries) + { + if (invoker == null) throw new ArgumentNullException(nameof(invoker)); + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (entries == null) throw new ArgumentNullException(nameof(entries)); - public ApiRequest Request { get => _request; } + _invoker = invoker; + _provider = provider; + _entries = entries; - public ApiResponse Response { get => Response; } + _options = invoker.Options ?? new ApiOptions(); - internal ApiContext() { } + } } diff --git a/Apewer/Web/ApiException.cs b/Apewer/Web/ApiException.cs index 22910b5..ab63cbd 100644 --- a/Apewer/Web/ApiException.cs +++ b/Apewer/Web/ApiException.cs @@ -9,7 +9,22 @@ namespace Apewer.Web public sealed class ApiException : Exception { - internal ApiException(string message) { } + string _status = null; + + /// 表示 API 状态。 + public string Status { get { return _status; } } + + internal ApiException(string message, string status = "exception") : base(FixMessage(message)) + { + _status = status; + } + + static string FixMessage(string message) + { + var msg = TextUtility.Trim(message); + if (string.IsNullOrEmpty(msg)) msg = "在执行 API 的过程中发生了未定义消息的错误。"; + return msg; + } } diff --git a/Apewer/Web/ApiFunction.cs b/Apewer/Web/ApiFunction.cs index 734f44d..bfd5849 100644 --- a/Apewer/Web/ApiFunction.cs +++ b/Apewer/Web/ApiFunction.cs @@ -57,7 +57,7 @@ namespace Apewer.Web psJson = new Class(ps); } } - if (psJson.HasValue) json.SetProperty("parameters", psJson.Value); + if (psJson) json.SetProperty("parameters", psJson.Value); } return json; } diff --git a/Apewer/Web/ApiInvoker.cs b/Apewer/Web/ApiInvoker.cs index 9a2ebff..50c1cdf 100644 --- a/Apewer/Web/ApiInvoker.cs +++ b/Apewer/Web/ApiInvoker.cs @@ -35,6 +35,9 @@ namespace Apewer.Web /// 异常捕获程序。 public Action Catcher { get; set; } + /// 输出前的检查。 + public ApiPreOutput PreOutput { get; set; } + /// 执行初始化程序,每个 ApiInvoker 实例仅执行一次初始化。 public void Initialize(Action action) { @@ -62,11 +65,8 @@ namespace Apewer.Web if (provider == null) return "未指定有效的服务程序。"; if (entries == null) return "未指定有效的入口。"; - var processor = new ApiProcessor(); - processor.Invoker = this; - processor.Entries = entries; - processor.Provider = provider; - processor.Catcher = Catcher; + var context = new ApiContext(this, provider, entries); + var processor = new ApiProcessor(context); return processor.Run(); } diff --git a/Apewer/Web/ApiMiddleware.cs b/Apewer/Web/ApiMiddleware.cs new file mode 100644 index 0000000..f79fc27 --- /dev/null +++ b/Apewer/Web/ApiMiddleware.cs @@ -0,0 +1,18 @@ +#if Middleware + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Web +{ + + /// 中间件。 + public abstract class ApiMiddleware + { + + } + +} + +#endif diff --git a/Apewer/Web/ApiOptions.cs b/Apewer/Web/ApiOptions.cs index 9911dc8..d1a3811 100644 --- a/Apewer/Web/ApiOptions.cs +++ b/Apewer/Web/ApiOptions.cs @@ -46,6 +46,9 @@ namespace Apewer.Web /// 默认值:-1,不使用 ApiOptions 限制。 public long MaxRequestBody { get; set; } = -1; + /// 输出前的检查。 + public ApiPreOutput PreOutput { get; set; } + /// 在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。 /// 默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。 public bool UpgradeHttps { get; set; } = false; diff --git a/Apewer/Web/ApiPreOutput.cs b/Apewer/Web/ApiPreOutput.cs new file mode 100644 index 0000000..cab0249 --- /dev/null +++ b/Apewer/Web/ApiPreOutput.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Web +{ + + /// 输出前的检查。 + /// True: 继续执行输出。
False: 终止输出。
+ public delegate bool ApiPreOutput(ApiContext context); + +} diff --git a/Apewer/Web/ApiProcessor.cs b/Apewer/Web/ApiProcessor.cs index a916057..bce4968 100644 --- a/Apewer/Web/ApiProcessor.cs +++ b/Apewer/Web/ApiProcessor.cs @@ -11,59 +11,59 @@ namespace Apewer.Web internal class ApiProcessor { - internal ApiInvoker Invoker = null; - internal ApiEntries Entries = null; - internal ApiProvider Provider = null; - internal Action Catcher = null; - ApiOptions Options = null; - - DateTime Begin = DateTime.UtcNow; - Uri Url = null; - HttpMethod Method = HttpMethod.NULL; - ApiRequest ApiRequest = null; - ApiResponse ApiResponse = null; - - internal ApiProcessor() + // in + private ApiContext _context = null; + + // temp + // private Uri _url = null; + // private HttpMethod _method = HttpMethod.NULL; + + // out + // private ApiEntry _entry = null; + // private ApiRequest _request = null; + // private ApiResponse _response = null; + // private ApiController _controller = null; + + internal ApiProcessor(ApiContext context) { + _context = context; } /// 执行处理程序,返回错误信息。 public string Run() { - if (Options == null) Options = new ApiOptions(); var error = Flow(); return error; } string Flow() { - // 传入字段。 - if (Provider == null) return "服务程序无效。"; - if (Invoker == null) return "调用器无效。"; - if (Entries == null) return "入口无效。"; - Options = Invoker.Options ?? new ApiOptions(); - Provider.Options = Options; - try { // 检查执行的前提条件,获取 Method 和 URL。 - var check = Check(); + Uri url = null; + HttpMethod method = HttpMethod.NULL; + var check = Check(ref method, ref url); if (!string.IsNullOrEmpty(check)) return check; - // 准备请求和响应模型。 - ApiRequest = GetRequest(Provider, Options, Method, Url); - ApiResponse = new ApiResponse(); - ApiResponse.Random = ApiRequest.Random; - ApiResponse.Application = ApiRequest.Application; - ApiResponse.Function = ApiRequest.Function; + // 准备请求模型。 + var request = GetRequest(_context.Provider, _context.Options, method, url); + _context.Request = request; + + // 准备响应模型。 + var response = new ApiResponse(); + response.Random = request.Random; + response.Application = request.Application; + response.Function = request.Function; + _context.Response = response; // 调用 API。 var invoke = Invoke(); if (!string.IsNullOrEmpty(invoke)) return invoke; // 输出。 - ApiResponse.Duration = Duration(); - Output(Provider, Options, ApiResponse, ApiRequest, Method); + response.Duration = Duration(_context.Beginning); + Output(_context.Provider, _context.Options, response, request, method); return null; } catch (Exception ex) @@ -74,9 +74,9 @@ namespace Apewer.Web } } - string Duration() + static string Duration(DateTime beginning) { - var span = DateTime.UtcNow - Begin; + var span = DateTime.Now - beginning; var ms = span.TotalMilliseconds; if (ms > 0D) { @@ -92,38 +92,38 @@ namespace Apewer.Web } } - string Check() + string Check(ref HttpMethod method, ref Uri url) { // 服务程序检查。 - var check = Provider.PreInvoke(); + var check = _context.Provider.PreInvoke(); if (!string.IsNullOrEmpty(check)) return check; // URL - Url = Provider.GetUrl(); - if (Url == null) return "URL 无效。"; + url = _context.Provider.GetUrl(); + if (url == null) return "URL 无效。"; - Method = Provider.GetMethod(); - if (Method == HttpMethod.NULL) return "HTTP 方法无效。"; - if (Method == HttpMethod.OPTIONS) return null; + method = _context.Provider.GetMethod(); + if (method == HttpMethod.NULL) return "HTTP 方法无效。"; + if (method == HttpMethod.OPTIONS) return null; // favicon.ico - var lowerPath = TextUtility.AssureStarts(TextUtility.Lower(Url.AbsolutePath), "/"); - if (!Options.AllowFavIcon) + var lowerPath = TextUtility.AssureStarts(TextUtility.Lower(url.AbsolutePath), "/"); + if (!_context.Options.AllowFavIcon) { if (lowerPath.StartsWith("/favicon.ico")) { - Output(Provider, Options, null, null, null); + Output(_context.Provider, _context.Options, null, null, null); return "已取消对 favicon.ico 的请求。"; } } // robots.txt - if (!Options.AllowRobots) + if (!_context.Options.AllowRobots) { if (lowerPath.StartsWith("/robots.txt")) { const string text = "User-agent: *\nDisallow: / \n"; - Output(Provider, Options, null, "text/plain", TextUtility.Bytes(text)); + Output(_context.Provider, _context.Options, null, "text/plain", TextUtility.Bytes(text)); return "已取消对 robots.txt 的请求。"; } } @@ -134,16 +134,12 @@ namespace Apewer.Web // 寻找入口。 string Invoke() { - var appName = ApiRequest.Application; - var funcName = ApiRequest.Function; - var random = ApiRequest.Random; - - Invoke(Entries.Get(appName)); + var appName = _context.Request.Application; + var application = _context.Entries.Get(appName); + Invoke(application); - ApiResponse.Duration = Duration(); - ApiResponse.Application = appName; - ApiResponse.Function = funcName; - ApiResponse.Random = random; + var duration = Duration(_context.Beginning); + _context.Response.Duration = duration; return null; } @@ -151,26 +147,29 @@ namespace Apewer.Web // 创建控制器。 void Invoke(ApiApplication application) { - var request = ApiRequest; - var response = ApiResponse; + var options = _context.Options; + var entries = _context.Entries; + var request = _context.Request; + var response = _context.Response; + var function = null as ApiFunction; var controller = null as ApiController; // Application 无效,尝试默认控制器和枚举。 if (application == null) { - var @default = Options.Default; + var @default = options.Default; if (@default == null) { // 没有指定默认控制器,尝试枚举。 response.Error("Invalid Application"); - if (Options.AllowEnumerate) response.Data = Enumerate(Entries.Enumerate(), Options); + if (options.AllowEnumerate) response.Data = Enumerate(entries.Enumerate(), options); return; } else { // 创建默认控制器。 - try { controller = CreateController(@default, request, response, Options); } + try { controller = CreateController(@default, request, response, options); } catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); } } } @@ -178,11 +177,11 @@ namespace Apewer.Web { // 创建控制器时候会填充 Controller.Request 属性,可能导致 Request.Function 被篡改,所以在创建之前获取 Function。 function = application.Get(request.Function); - try { controller = CreateController(application.Type, request, response, Options); } + try { controller = CreateController(application.Type, request, response, options); } catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); } } if (controller == null) response.Error("创建控制器实例失败。"); - else Invoke(controller, application, function, Options, request, response); + else Invoke(controller, application, function, options, request, response); RuntimeUtility.Dispose(controller); } @@ -296,13 +295,14 @@ namespace Apewer.Web { var ex = exception.InnerException; - if (Catcher != null) + var catcher = _context.Invoker.Catcher; + if (catcher != null) { ApiUtility.Exception(response, ex, false); try { var apiCatch = new ApiCatch(controller, options, ex); - Catcher.Invoke(apiCatch); + catcher.Invoke(apiCatch); } catch { } return; @@ -511,54 +511,34 @@ namespace Apewer.Web return merged; } - internal static string ExportJson(ApiResponse response, ApiOptions options) - { - if (response == null) return "{}"; - if (string.IsNullOrEmpty(response.Status)) response.Status = "ok"; - var json = Json.NewObject(); - // 执行时间。 - if (options.WithClock) + internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes) + { + var preWrite = provider.PreWrite(); + if (!string.IsNullOrEmpty(preWrite)) return; + + var responsePreOutput = response.PreOutput; + if (responsePreOutput != null) { - json.SetProperty("clock", ClockUtility.Lucid(DateTime.Now)); + var @continue = responsePreOutput.Invoke(_context); + if (!@continue) return; } - // 持续时间。 - if (options.WithDuration) + var invokerPreOutput = _context.Invoker.PreOutput; + if (invokerPreOutput != null) { - if (response.Duration.NotEmpty()) json.SetProperty("duration", response.Duration); + var @continue = invokerPreOutput.Invoke(_context); + if (!@continue) return; } - // 随机值。 - var random = response.Random; - if (!string.IsNullOrEmpty(random)) json.SetProperty("random", random); - - // 调用。 - if (options.WithTarget) + var optionsPreOutput = _context.Options.PreOutput; + if (optionsPreOutput != null) { - json.SetProperty("application", response.Application); - json.SetProperty("function", response.Function); + var @continue = optionsPreOutput.Invoke(_context); + if (!@continue) return; } - // 状态。 - json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.Empty : response.Status.ToLower())); - if (!string.IsNullOrEmpty(response.Message)) json.SetProperty("message", response.Message); - - // 用户数据。 - if (response.Message == "exception" && !options.WithException) json.SetProperty("data"); - else json.SetProperty("data", response.Data); - - var indented = response.Indented || options.JsonIndent; - var text = json.ToString(indented); - return text; - } - - internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes) - { - var preWrite = provider.PreWrite(); - if (!string.IsNullOrEmpty(preWrite)) return; - var headers = PrepareHeaders(options, response); foreach (var header in headers) provider.SetHeader(header.Key, header.Value); @@ -571,11 +551,32 @@ namespace Apewer.Web provider.Sent(); } - internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method) + internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method) { var preWrite = provider.PreWrite(); if (!string.IsNullOrEmpty(preWrite)) return; + var responsePreOutput = response.PreOutput; + if (responsePreOutput != null) + { + var @continue = responsePreOutput.Invoke(_context); + if (!@continue) return; + } + + var invokerPreOutput = _context.Invoker.PreOutput; + if (invokerPreOutput != null) + { + var @continue = invokerPreOutput.Invoke(_context); + if (!@continue) return; + } + + var optionsPreOutput = _context.Options.PreOutput; + if (optionsPreOutput != null) + { + var @continue = optionsPreOutput.Invoke(_context); + if (!@continue) return; + } + // 设置头。 var headers = PrepareHeaders(options, response, request); foreach (var header in headers) provider.SetHeader(header.Key, header.Value); @@ -589,7 +590,7 @@ namespace Apewer.Web return; } - var text = ExportJson(response, options); + var text = ApiUtility.ToJson(response, options); var bytes = TextUtility.Bytes(text); provider.SetCache(0); provider.SetContentType("text/json; charset=utf-8"); diff --git a/Apewer/Web/ApiResponse.cs b/Apewer/Web/ApiResponse.cs index ac84c94..7c8af58 100644 --- a/Apewer/Web/ApiResponse.cs +++ b/Apewer/Web/ApiResponse.cs @@ -73,6 +73,13 @@ namespace Apewer.Web #endregion + #region Function + + /// 设置输出前的检查。 + public ApiPreOutput PreOutput { get; set; } + + #endregion + } } diff --git a/Apewer/Web/ApiServiceDescriptor.cs b/Apewer/Web/ApiServiceDescriptor.cs new file mode 100644 index 0000000..d092d6d --- /dev/null +++ b/Apewer/Web/ApiServiceDescriptor.cs @@ -0,0 +1,92 @@ +#if Middleware + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Web +{ + + /// API 服务描述。 + public sealed class ApiServiceDescriptor : IToJson + { + + /// 订阅器的生命周期。 + public ApiServiceLifetime Lifetime { get; private set; } + + /// 服务的类型。 + public Type ServiceType { get; private set; } + + /// 实现服务的类型。 + public Type ImplementationType { get; private set; } + + public object ImplementationInstance + { + get; + } + + public Func ImplementationFactory + { + get; + } + + /// 创建订阅器的实例。 + /// API 调用器。 + /// 订阅器的生命周期。 + /// 服务的类型。 + /// 实现服务的类型。 + /// + /// + 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; + } + + /// 创建订阅器的实例。 + /// API 调用器。 + /// 订阅器的生命周期。 + /// 服务的类型。 + /// 实现服务的方法。 + /// + /// + internal ApiServiceDescriptor(ApiServiceLifetime lifetime, Type service, Func生成 JSON 实例。 + public Json ToJson() + { + var json = new Json(); + json["Lifetime"] = Lifetime.ToString(); + json["Service"] = ServiceType.FullName; + json["Implementation"] = Implementation.FullName; + return json; + } + + } + +} + +#endif diff --git a/Apewer/Web/ApiServiceLifetime.cs b/Apewer/Web/ApiServiceLifetime.cs new file mode 100644 index 0000000..358e699 --- /dev/null +++ b/Apewer/Web/ApiServiceLifetime.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Web +{ + + /// 服务的生命周期。 + public enum ApiServiceLifetime + { + + /// 对所有请求使用同一个实例。 + Singleton, + + /// 对每个请求使用同一个实例。 + Scoped, + + /// 对每个请求里的每次声明创建新实例。 + Transient + + } + +} diff --git a/Apewer/Web/ApiUtility.cs b/Apewer/Web/ApiUtility.cs index 7f2fe30..1e9b995 100644 --- a/Apewer/Web/ApiUtility.cs +++ b/Apewer/Web/ApiUtility.cs @@ -763,6 +763,51 @@ namespace Apewer.Web response.StopReturn = true; } + /// 生成 Response 的 Json 实例。 + public static string ToJson(this ApiResponse response, ApiOptions options = null) + { + if (response == null) return "{}"; + if (string.IsNullOrEmpty(response.Status)) response.Status = "ok"; + + var json = Json.NewObject(); + if (options == null) options = new ApiOptions(); + + // 执行时间。 + if (options.WithClock) + { + json.SetProperty("clock", ClockUtility.Lucid(DateTime.Now)); + } + + // 持续时间。 + if (options.WithDuration) + { + if (response.Duration.NotEmpty()) json.SetProperty("duration", response.Duration); + } + + // 随机值。 + var random = response.Random; + if (!string.IsNullOrEmpty(random)) json.SetProperty("random", random); + + // 调用。 + if (options.WithTarget) + { + json.SetProperty("application", response.Application); + json.SetProperty("function", response.Function); + } + + // 状态。 + json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.Empty : response.Status.ToLower())); + if (!string.IsNullOrEmpty(response.Message)) json.SetProperty("message", response.Message); + + // 用户数据。 + if (response.Message == "exception" && !options.WithException) json.SetProperty("data"); + else json.SetProperty("data", response.Data); + + var indented = response.Indented || options.JsonIndent; + var text = json.ToString(indented); + return text; + } + #endregion #region ApiModel diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs index 58ab8d9..3f5bb47 100644 --- a/Apewer/_Extensions.cs +++ b/Apewer/_Extensions.cs @@ -124,25 +124,25 @@ public static class Extensions public static string Camel(this string @this) => TextUtility.Camel(@this); /// 转换为 Boolean 值。 - public static bool Boolean(this object @this) => NumberUtility.Boolean(@this); + public static bool Boolean(this string @this) => NumberUtility.Boolean(@this); /// 转换为 Byte 值。 - public static byte Byte(this object @this) => NumberUtility.Byte(@this); + public static byte Byte(this string @this) => NumberUtility.Byte(@this); /// 转换为 Int32 值。 - public static int Int32(this object @this) => NumberUtility.Int32(@this); + public static int Int32(this string @this) => NumberUtility.Int32(@this); /// 转换为 Int64 值。 - public static long Int64(this object @this) => NumberUtility.Int64(@this); + public static long Int64(this string @this) => NumberUtility.Int64(@this); /// 转换为 Decimal 值。 - public static decimal Decimal(this object @this) => NumberUtility.Decimal(@this); + public static decimal Decimal(this string @this) => NumberUtility.Decimal(@this); /// 转换为单精度浮点值。 - public static float Float(this object @this) => NumberUtility.Float(@this); + public static float Float(this string @this) => NumberUtility.Float(@this); /// 转换为双精度浮点值。 - public static double Double(this object @this) => NumberUtility.Double(@this); + public static double Double(this string @this) => NumberUtility.Double(@this); /// 将文本转换为字节数组,默认使用 UTF-8。 public static byte[] Bytes(this string @this, Encoding encoding = null) => TextUtility.Bytes(@this, encoding); @@ -280,6 +280,30 @@ public static class Extensions /// public static DateTime DateTime(this long stamp, DateTimeKind kind = DateTimeKind.Unspecified, bool throwException = true) => ClockUtility.FromStamp(stamp, kind, throwException); + #region Nullable + + /// 转换为易于阅读的文本。 + /// 格式:1970-01-01 00:00:00.000 + public static string Lucid(this DateTime? @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Lucid(@this.Value, date, time, seconds, milliseconds); + + /// 转换为紧凑的文本。 + public static string Compact(this DateTime? @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds); + + /// 获取毫秒时间戳。 + public static long Stamp(this DateTime? @this, bool byMilliseconds = true) => @this == null ? default : ClockUtility.Stamp(@this.Value, byMilliseconds); + + /// 转换为易于阅读的文本。 + /// 格式:1970-01-01 00:00:00.000 + public static string Lucid(this Class @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Lucid(@this.Value, date, time, seconds, milliseconds); + + /// 转换为紧凑的文本。 + public static string Compact(this Class @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds); + + /// 获取毫秒时间戳。 + public static long Stamp(this Class @this, bool byMilliseconds = true) => @this == null ? default : ClockUtility.Stamp(@this.Value, byMilliseconds); + + #endregion + #endregion #region ArrayBuilder @@ -534,10 +558,10 @@ public static class Extensions #region Source /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 - public static Class DateTime(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : Query.DateTime(@this.Value(row, column)); + public static Class DateTime(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : ClockUtility.DateTime(@this.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。 - public static Class DateTime(this IQuery @this, int row, string column) => @this == null ? null : Query.DateTime(@this.Value(row, column)); + public static Class DateTime(this IQuery @this, int row, string column) => @this == null ? null : ClockUtility.DateTime(@this.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 public static Int32 Int32(this IQuery @this, int row = 0, int column = 0) => @this == null ? 0 : NumberUtility.Int32(@this.Value(row, column)); @@ -564,22 +588,15 @@ public static class Extensions public static Double Double(this IQuery @this, int row, string column) => @this == null ? 0D : NumberUtility.Double(@this.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 - public static string Text(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : Query.Text(@this.Value(row, column)); + public static string Text(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : TextUtility.Text(@this.Value(row, column)); /// 获取默认表中指定单元格的内容。从第 0 行开始。 - public static string Text(this IQuery @this, int row, string column) => @this == null ? null : Query.Text(@this.Value(row, column)); - - /// 转换 数组,每行记录为一个 ObjectSet 对象。 - /// 当参数 table 无效时返回 0 长度的 数组。 - public static ObjectSet[] ObjectSet(this DataTable @this) => SourceUtility.ObjectSet(@this); + public static string Text(this IQuery @this, int row, string column) => @this == null ? null : TextUtility.Text(@this.Value(row, column)); /// 转换 数组,每行记录为一个 ObjectSet 对象。 /// 当参数 table 无效时返回 0 长度的 数组。 public static ObjectSet[] ObjectSet(this IQuery @this) => @this == null ? null : SourceUtility.ObjectSet(@this.Table); - /// 转换 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。 - public static string Csv(this DataTable table, bool withHead = false) => SourceUtility.Csv(table, withHead); - /// 转换 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。 public static string Csv(this IQuery query, bool withHead = false) => query == null ? null : SourceUtility.Csv(query.Table, withHead); diff --git a/ChangeLog.md b/ChangeLog.md index 452fe58..da70e18 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,39 @@  ### 最新提交 +### 6.7.3 +- 问题修正 + - ClockUtility:修正 Query.DateTime() 出现 null.ToString 问题; + - TableAttribute:去除了部分类型不应存在的 TableAttribute; + - SqlClient:修正建库功能,现在可以正确设置兼容性级别为 100(SQL Server 2008); + - TextUtility:统一 Merge 和 Join 方法,解决日志中有完整类名的问题。 +- 新特性 + - ApiException:增加了参数 status,用于指定要输出的状态; + - ApiInvoker:增加 PreOutput,按 Response -> Invoker -> Options 的顺序执行检查; + - BytesUtility:计算 AES256 支持使用 Stream 作为参数,以支持大文件; + - ClockUtility:扩展方法增加 DateTime? 和 Class 类型; + - CollectionUtility:增加 Find、FindAll、Map 和 ForEach 扩展方法; + - DbClient:增加 ThrowAdoException 属性,可要求 ADO 方法抛出异常; + - FormsUtility:增加获取 GUI 字体的静态属性; + - Json:修正 From(IList) 参数元素是 null 时的报错; + - Json:增加自定义 DateTime 的序列化和反序列化功能; + - MySql:构造函数现在支持指定服务器的端口号; + - NumberUtility:增加 ToChinesePrice 方法,用于生成中文大写金额; + - RuntimeUtility:增加 GetTypeOfArrayItem 方法,获取数组的元素类型; + - RuntimeUtility:现在抛出正确的 InnerException,而不是上层 Exception; + - Sqlite:调用构造函数时,如果文件不存在,现在会自动尝试创建文件,而不是立即报错; + - StorageUtility:增加 AssureFile 方法,创建空文件; + - StorageUtility:增加 HasZoneIdentifier,用于检测是否包含标记; + - SourceUtility:增加 ObjectSet[] 转为 dynamic[] 的扩展方法; + - SourceUtility:增加 Compute 方法,计算数学文本表达式; + - 增加扩展方法 Image.Resize; + - 增加扩展方法 string[].Trim。 +- 已迁移特性 + - 迁移 Query 方法,改为 DataTable 的扩展方法。 +- 弃用特性 + - 去除 object 转数值类型的扩展方法,现在扩展方法只支持明确支持的类型。 + - Class 去除了无用的 HasValue 属性 + ### 6.7.2 - 问题修正 - 修正了 SQLite 的构造函数中的 path 参数;