Browse Source

Apewer-6.7.3

dev
王厅 2 years ago
parent
commit
306a6c3066
  1. 43
      Apewer.Source/Source/DbClient.cs
  2. 12
      Apewer.Source/Source/MySql.cs
  3. 2
      Apewer.Source/Source/SqlClient.cs
  4. 54
      Apewer.Source/Source/Sqlite.cs
  5. 118
      Apewer.Windows/Internals/RegHelper.cs
  6. 16
      Apewer.Windows/Surface/FormsUtility.cs
  7. 55
      Apewer.Windows/Surface/ImageUtility.cs
  8. 2
      Apewer/Apewer.props
  9. 30
      Apewer/BytesUtility.cs
  10. 9
      Apewer/Class.cs
  11. 45
      Apewer/ClockUtility.cs
  12. 226
      Apewer/CollectionUtility.cs
  13. 8
      Apewer/Internals/NtfsUnlocker.cs
  14. 51
      Apewer/Json.cs
  15. 1
      Apewer/Network/MailAddress.cs
  16. 1
      Apewer/Network/MailClient.cs
  17. 1
      Apewer/Network/MailRecord.cs
  18. 249
      Apewer/NumberUtility.cs
  19. 67
      Apewer/RuntimeUtility.cs
  20. 1
      Apewer/Source/HttpRecord.cs
  21. 4
      Apewer/Source/IDbAdo.cs
  22. 217
      Apewer/Source/Query.cs
  23. 656
      Apewer/Source/SourceUtility.cs
  24. 29
      Apewer/StorageUtility.cs
  25. 83
      Apewer/TextUtility.cs
  26. 63
      Apewer/Web/ApiContext.cs
  27. 17
      Apewer/Web/ApiException.cs
  28. 2
      Apewer/Web/ApiFunction.cs
  29. 10
      Apewer/Web/ApiInvoker.cs
  30. 18
      Apewer/Web/ApiMiddleware.cs
  31. 3
      Apewer/Web/ApiOptions.cs
  32. 12
      Apewer/Web/ApiPreOutput.cs
  33. 203
      Apewer/Web/ApiProcessor.cs
  34. 7
      Apewer/Web/ApiResponse.cs
  35. 92
      Apewer/Web/ApiServiceDescriptor.cs
  36. 23
      Apewer/Web/ApiServiceLifetime.cs
  37. 45
      Apewer/Web/ApiUtility.cs
  38. 53
      Apewer/_Extensions.cs
  39. 33
      ChangeLog.md

43
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
/// <summary>更改已打开的数据库。</summary>
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<IsolationLevel> 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
/// <summary>提交事务。</summary>
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
/// <summary>从挂起状态回滚事务。</summary>
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
/// <summary>允许 ADO 抛出异常,取代返回错误信息。</summary>
/// <value>FALSE(默认值)</value>
public virtual bool ThrowAdoException { get; set; }
/// <summary>查询。</summary>
/// <param name="sql">SQL 语句。</param>
/// <param name="parameters">为 SQL 语句提供的参数。</param>
@ -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);
}
}

12
Apewer.Source/Source/MySql.cs

@ -51,6 +51,18 @@ namespace Apewer.Source
_connstr = cs;
}
/// <summary>构建连接字符串以创建实例。</summary>
/// <exception cref="ArgumentNullException"></exception>
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;
}
/// <summary></summary>
protected override void ClearPool(bool all = false)
{

2
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);
// 设置恢复默认为“简单”

54
Apewer.Source/Source/Sqlite.cs

@ -50,6 +50,10 @@ namespace Apewer.Source
/// <param name="pass">连接数据库的密码,使用内存数据库时此参数将被忽略。</param>
/// <param name="timeout">超时设定。</param>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="IOException"></exception>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="PathTooLongException"></exception>
/// <exception cref="UnauthorizedAccessException"></exception>
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
/// <summary>保存当前数据库到文件,若文件已存在则将重写文件。</summary>
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);
}
/// <summary>保存当前数据库到目标数据库。</summary>
public bool Save(Sqlite destination) => string.IsNullOrEmpty(Backup(this, destination));
public string Save(Sqlite destination) => Backup(this, destination);
/// <summary>加载文件到当前数据库。</summary>
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);
}
/// <summary>加载源数据库到当前数据库。</summary>
public bool Load(Sqlite source) => string.IsNullOrEmpty(Backup(source, this));
public string Load(Sqlite source) => Backup(source, this);
/// <summary>备份数据库,返回错误信息。</summary>
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;
}
}

118
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
{
/// <summary>注册表。</summary>
static class RegHelper
{
/// <summary>用户登录后的启动项。</summary>
const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run";
/// <summary>HKEY_CURRENT_USER</summary>
/// <remarks>当前用户的信息。</remarks>
static RegistryKey CurrentUser { get => Registry.CurrentUser; }
/// <summary>HKEY_LOCAL_MACHINE</summary>
/// <remarks>系统信息,对所有用户生效,设置需要管理员权限。</remarks>
static RegistryKey LocalMachine { get => Registry.LocalMachine; }
static RegistryKey OpenSubKey(RegistryKey root, string key, bool write = false)
{
var segs = key.Split('\\', '/');
var queue = new Queue<string>(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;
}
/// <summary>获取字符串。</summary>
/// <param name="root">注册表存储区。</param>
/// <param name="key">路径。</param>
/// <param name="name">名称。</param>
/// <returns>字符串的值。获取失败时返回 NULL 值。</returns>
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;
}
/// <summary>设置字符串,指定 value 为 NULL 可删除该值。</summary>
/// <param name="root">注册表存储区。</param>
/// <param name="key">路径。</param>
/// <param name="name">名称。</param>
/// <param name="value">值。</param>
/// <returns>错误信息。设置成功时返回 NULL 值。</returns>
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;
}
}
/// <summary>已启用自动启动。</summary>
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);
}
}
}
}

16
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 = "";
/// <summary>存在微软雅黑字体。</summary>
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"); }
/// <summary>获取默认字体名称。</summary>
public static string DefaultFontName
@ -853,8 +852,17 @@ namespace Apewer.Surface
}
}
/// <summary>获取默认字体名称。</summary>
public static string GuiFontName { get => Registry.GetValue(GuiFontRegKey, "GUIFont.Facename", "Arial") as string; }
/// <summary>获取默认字体大小。</summary>
public static float GuiFontSize { get => Convert.ToSingle(Registry.GetValue(GuiFontRegKey, "GUIFont.Height", 9F)); }
#if NETFX || NETCORE
/// <summary>获取默认字体。</summary>
public static Font GuiFont { get => new Font(GuiFontName, GuiFontSize); }
/// <summary>获取默认字体。</summary>
public static Font DefaultFont
{

55
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
{
/// <summary>图像实用工具。</summary>
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);
}
/// <summary>调整图像尺寸,生成新图像。</summary>
/// <param name="image">原图像。</param>
/// <param name="width">新图像的宽度,在缩放时此参数用于限定最大宽度。</param>
/// <param name="height">新图像的高度,在缩放时此参数用于限定最大高度。</param>
/// <param name="scale">保持原比例进行缩放。</param>
/// <returns>新图像。</returns>
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;
}
}
/// <summary>生成二维码图像,可指定色块边长像素值和纠错率,失败时返回 NULL 值。</summary>
public static Bitmap GenerateQrCode(string text, int block = 8)
{

2
Apewer/Apewer.props

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

30
Apewer/BytesUtility.cs

@ -776,6 +776,36 @@ namespace Apewer
#region AES
private static void Aes256(byte[] key, byte[] salt, Func<RijndaelManaged, ICryptoTransform> 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();
}
}
}
}
/// <summary>执行 AES 加密。</summary>
public static void Aes256Encrypt(Stream input, Stream output, byte[] key) => Aes256(key, key, rm => rm.CreateEncryptor(), input, output);
/// <summary>执行 AES 解密。</summary>
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);

9
Apewer/Class.cs

@ -13,12 +13,6 @@ namespace Apewer
/// <summary>装箱对象。</summary>
public T Value { get; set; }
/// <summary></summary>
public bool IsNull { get { return Value == null; } }
/// <summary></summary>
public bool HasValue { get { return Value != null; } }
/// <summary>创建默认值。</summary>
public Class(T value = default, bool tryEquals = true, bool tryHashCode = true)
{
@ -95,7 +89,6 @@ namespace Apewer
public int CompareTo(Class<T> other)
{
if (Value == null) throw new MissingMemberException(typeof(T).FullName, nameof(Value));
if (other == null || !other.HasValue) return 1;
if (Value is IComparable) return ((IComparable)Value).CompareTo(other.Value);
if (Value is IComparable<T>) return ((IComparable<T>)Value).CompareTo(other.Value);
@ -119,7 +112,7 @@ namespace Apewer
var text = instance as Class<string>;
if (text != null) return !string.IsNullOrEmpty(text.Value);
return instance.HasValue;
return instance.NotNull();
}
/// <summary>从 <see cref="Class{T}"/> 到 T 的隐式转换。</summary>

45
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
/// <summary>尝试转换内容为 DateTime 实例。</summary>
public static Class<DateTime> 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<DateTime>(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; }
/// <summary>获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为本地时间。</summary>
public static DateTime Now { get => DateTime.Now; }
public static DateTime Now { get => System.DateTime.Now; }
/// <summary>获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,表示为协调通用时间 (UTC)。</summary>
public static DateTime UtcNow { get => DateTime.UtcNow; }
public static DateTime UtcNow { get => System.DateTime.UtcNow; }
/// <summary>创建一个 DateTime 对象,该对象设置为 1970-01-01 00:00:00.000。</summary>
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); }
/// <summary>表示当前本地日期的文本,显示为易于阅读的格式。</summary>
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); } }
/// <summary>表示当前本地时间的文本,显示为紧凑的格式。</summary>
public static string CompactNow { get => Compact(Now); }
@ -201,7 +226,7 @@ namespace Apewer
public static string CompactUtc { get => Compact(UtcNow); }
/// <summary>表示当前本地日期的文本,显示为紧凑的格式。</summary>
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); } }
/// <summary>转换 DateTime 对象到易于阅读的文本。</summary>
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)

226
Apewer/CollectionUtility.cs

@ -577,6 +577,232 @@ namespace Apewer
#endregion
#region Find
/// <summary>根据条件筛选,将符合条件的元素组成新数组。</summary>
public static T[] FindAll<T>(this T[] array, Predicate<T> match) => System.Array.FindAll<T>(array, match);
/// <summary>根据条件筛选,找到第一个符合条件的元素。</summary>
public static T Find<T>(this T[] array, Predicate<T> match) => System.Array.Find<T>(array, match);
#endregion
#region Map
/// <summary>遍历集合,转换元素,生成新数组。</summary>
/// <typeparam name="TIn">输入的元素类型。</typeparam>
/// <typeparam name="TOut">输出的元素类型。</typeparam>
/// <param name="collection">要遍历的集合。</param>
/// <param name="selector">转换程序。</param>
/// <returns>新数组的元素类型。</returns>
public static TOut[] Map<TIn, TOut>(this IEnumerable<TIn> collection, Func<TIn, TOut> 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<TOut>();
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
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="callback">回调。参数为本次遍历的元素。</param>
public static void ForEach<T>(this IEnumerable<T> collection, Action<T> callback)
{
if (collection == null || callback == null) return;
ForEach<T>(collection, (item, index) =>
{
callback.Invoke(item);
return true;
});
}
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="callback">回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。</param>
public static void ForEach<T>(this IEnumerable<T> collection, Action<T, int> callback)
{
if (collection == null || callback == null) return;
ForEach<T>(collection, (item, index) =>
{
callback.Invoke(item, index);
return true;
});
}
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="callback">回调。参数为本次遍历的元素。返回 true 将继续遍历,返回 false 打断遍历。</param>
public static void ForEach<T>(this IEnumerable<T> collection, Func<T, bool> callback)
{
if (collection == null || callback == null) return;
ForEach<T>(collection, (item, index) =>
{
var result = callback.Invoke(item);
return result;
});
}
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="callback">回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。返回 true 将继续遍历,返回 false 打断遍历。</param>
public static void ForEach<T>(this IEnumerable<T> collection, Func<T, int, bool> 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;
}
}
}
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="limit">每次遍历的元素数量。</param>
/// <param name="callback">回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。</param>
public static void ForEach<T>(this IEnumerable<T> collection, int limit, Action<T[]> callback)
{
if (collection == null || callback == null) return;
ForEach<T>(collection, limit, (items, index) =>
{
callback.Invoke(items);
return true;
});
}
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="limit">每次遍历的元素数量。</param>
/// <param name="callback">回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。</param>
public static void ForEach<T>(this IEnumerable<T> collection, int limit, Action<T[], int> callback)
{
if (collection == null || callback == null) return;
ForEach<T>(collection, limit, (items, index) =>
{
callback.Invoke(items, index);
return true;
});
}
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="limit">每次遍历的元素数量。</param>
/// <param name="callback">回调。参数为本次遍历的元素。返回 true 将继续遍历,返回 false 打断遍历。</param>
public static void ForEach<T>(this IEnumerable<T> collection, int limit, Func<T[], bool> callback)
{
if (collection == null || callback == null) return;
ForEach<T>(collection, limit, (items, index) =>
{
var result = callback.Invoke(items);
return result;
});
}
/// <summary>遍历每个元素,执行回调。</summary>
/// <typeparam name="T">元素的类型。</typeparam>
/// <param name="collection">元素的集合。</param>
/// <param name="limit">每次遍历的元素数量。</param>
/// <param name="callback">回调。参数为本次遍历的元素和遍历的索引值,索引值从 0 开始。返回 true 将继续遍历,返回 false 打断遍历。</param>
public static void ForEach<T>(this IEnumerable<T> collection, int limit, Func<T[], int, bool> 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<T>(collection);
var index = 0;
while (queue.Count > 0)
{
var list = new List<T>(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
}
}

8
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;

51
Apewer/Json.cs

@ -38,7 +38,7 @@ namespace Apewer
[NonSerialized]
private static bool _forceall = true;
/// <summary>允许产生 Exception,默认为不允许。</summary>
/// <summary>允许抛出 Exception,默认为不允许。</summary>
public static bool AllowException { get { return _throw; } set { _throw = value; } }
/// <summary>当存在递归引用时候包含递归项。指定为 True 时递归项为 Null 值,指定为 False 时不包含递归项。默认值:False。</summary>
@ -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
/// <summary>自定义 DateTime 序列化程序。</summary>
public static Func<DateTime, string> DateTimeSerializer { get; set; }
/// <summary>自定义 DateTime 反序列化程序。</summary>
public static Func<string, DateTime> DateTimeDeserializer { get; set; }
/// <summary>序列化 DateTime 实例。</summary>
public static string SerializeDateTime(DateTime dateTime)
{
var serializer = DateTimeSerializer;
if (serializer != null) return serializer.Invoke(dateTime);
return ClockUtility.Lucid(dateTime);
}
/// <summary>序列化 DateTime 实例。</summary>
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
}
}

1
Apewer/Network/MailAddress.cs

@ -8,7 +8,6 @@ namespace Apewer.Network
{
/// <summary>邮件地址。</summary>
[Table("_mail_address")]
[Serializable]
public sealed class MailAddress : Record
{

1
Apewer/Network/MailClient.cs

@ -9,7 +9,6 @@ namespace Apewer.Network
{
/// <summary>简单邮件传输协议客户端。</summary>
[Table("_mail_client")]
[Serializable]
public sealed class MailClient : Record
{

1
Apewer/Network/MailRecord.cs

@ -8,7 +8,6 @@ namespace Apewer.Network
{
/// <summary>邮件记录。</summary>
[Table("_mail_record")]
[Serializable]
public class MailRecord : Record
{

249
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 表达式计算
/// <summary>计算文本表达式,以 Int64 输出结果。</summary>
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);
}
/// <summary>计算文本表达式,以 Double 输出结果。</summary>
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
/// <summary>平均值。</summary>
public static double Average(params double[] values)
{
@ -392,41 +415,243 @@ namespace Apewer
return false;
}
private static Tout Try<Tin, Tout>(Tin value, Func<Tin, Tout> func)
{
try { return func.Invoke(value); }
catch { return default(Tout); }
}
/// <summary>获取单精度浮点对象。</summary>
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));
}
/// <summary>获取单精度浮点对象。</summary>
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));
}
/// <summary>获取双精度浮点对象。</summary>
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);
}
/// <summary>获取 Decimal 对象。</summary>
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));
}
/// <summary>获取 Byte 对象。</summary>
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);
}
/// <summary>获取 SByte 对象。</summary>
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);
}
/// <summary>获取 Int16 对象。</summary>
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);
}
/// <summary>获取 UInt16 对象。</summary>
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);
}
/// <summary>获取 Int32 对象。</summary>
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);
}
/// <summary>获取 UInt32 对象。</summary>
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);
}
/// <summary>获取 Int64 对象。</summary>
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);
}
/// <summary>获取 UInt64 对象。</summary>
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 转为字符串
/// <summary>转为大写中文金额。</summary>
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

67
Apewer/RuntimeUtility.cs

@ -488,6 +488,32 @@ namespace Apewer
return true;
}
/// <summary>获取数组元素的类型。</summary>
/// <remarks>参数必须是正确的数组类型。</remarks>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentNullException"></exception>
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
/// <summary>将代码生成为程序集。</summary>
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;

1
Apewer/Source/HttpRecord.cs

@ -7,7 +7,6 @@ namespace Apewer.Source
{
/// <summary></summary>
[Table]
[Serializable]
public class HttpRecord : Record
{

4
Apewer/Source/IDbAdo.cs

@ -10,6 +10,10 @@ namespace Apewer.Source
public interface IDbAdo : IDisposable
{
/// <summary>允许 ADO 抛出异常,取代返回错误信息。</summary>
/// <value>FALSE(默认值)</value>
bool ThrowAdoException { get; set; }
#region Connection
/// <summary>获取连接。</summary>

217
Apewer/Source/Query.cs

@ -92,134 +92,30 @@ namespace Apewer.Source
#region Value
/// <summary>获取默认表中第 0 行、第 0 列的单元格内容。</summary>
public object Value()
{
if (_disposed) return null;
return Value(0, 0);
}
public object Value() => Table.Value(0, 0);
/// <summary>获取默认表中指定行中第 0 列的内容。</summary>
/// <param name="rowIndex">行索引,从 0 开始。</param>
public object Value(int rowIndex)
{
if (_disposed) return null;
return Value(rowIndex, 0);
}
public object Value(int rowIndex) => Table.Value(rowIndex, 0);
/// <summary>获取默认表中第 0 行指定列的内容。</summary>
/// <param name="columnName">列名称/字段名称,此名称不区分大小写。</param>
public object Value(string columnName)
{
if (_disposed) return null;
return Value(0, columnName);
}
public object Value(string columnName) => Table.Value(0, columnName);
/// <summary>获取默认表中指定单元格的内容。</summary>
/// <param name="rowIndex">行索引,从 0 开始。</param>
/// <param name="columnIndex">列索引,从 0 开始。</param>
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);
/// <summary>获取默认表中指定单元的内容。</summary>
/// <param name="rowIndex">行索引,从 0 开始。</param>
/// <param name="columnName">列名称/字段名称,此名称不区分大小写。</param>
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;
}
/// <summary>搜索默认表。</summary>
/// <param name="conditionColumn">搜索条件:列名。</param>
/// <param name="conditionValue">搜索条件:列值。</param>
/// <param name="resultColumn">搜索结果。</param>
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;
}
/// <summary>搜索默认表。</summary>
/// <param name="conditionColumn">搜索条件:列名。</param>
/// <param name="conditionValue">搜索条件:列值。</param>
/// <param name="resultColumn">搜索结果的列名。</param>
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
/// <summary>搜索默认表。</summary>
/// <param name="conditionColumn">搜索条件:列名。</param>
/// <param name="conditionValue">搜索条件:列值。</param>
/// <param name="resultColumn">搜索结果。</param>
private string Text(int conditionColumn, string conditionValue, int resultColumn)
{
var value = Value(conditionColumn, conditionValue, resultColumn);
return Text(value);
}
/// <summary>释放系统资源。</summary>
public void Dispose()
{
@ -236,28 +132,6 @@ namespace Apewer.Source
// GC.SuppressFinalize(this);
}
/// <summary>当不指定格式化程序时,自动根据类型选择预置的格式化程序。</summary>
private static Func<object, T> GetValueFormatter<T>()
{
var type = typeof(T);
if (type.Equals(typeof(string))) return TextFormatter<T>;
else return ForceFormatter<T>;
}
/// <summary>获取指定列的所有值,无效值不加入结果。</summary>
public T[] ReadColumn<T>(int column = 0, Func<object, T> formatter = null) => SourceUtility.Column(this, (r) => (formatter ?? GetValueFormatter<T>()).Invoke(Value(r, column)));
/// <summary>获取指定列的所有值,无效值不加入结果。</summary>
/// <exception cref="ArgumentNullException"></exception>
public T[] ReadColumn<T>(string column, Func<object, T> formatter = null) => SourceUtility.Column(this, (r) => (formatter ?? GetValueFormatter<T>()).Invoke(Value(r, column)));
/// <summary>获取指定列的所有值,无效值不加入结果。</summary>
public string[] ReadColumn(int column = 0) => SourceUtility.Column(this, (r) => this.Text(r, column));
/// <summary>获取指定列的所有值,无效值不加入结果。</summary>
/// <exception cref="ArgumentNullException"></exception>
public string[] ReadColumn(string column) => SourceUtility.Column(this, (r) => this.Text(r, column));
#endregion
#region 模型化、IToJson
@ -265,52 +139,17 @@ namespace Apewer.Source
/// <summary>转换为 Json 对象。</summary>
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<T>(object input) => (T)input;
private static T TextFormatter<T>(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> 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<DateTime>(result) : null;
}
#endregion
}
}

656
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
/// <summary>读取所有行,生成列表。</summary>
public static T[] Fill<T>(this IQuery query) where T : class, new()
@ -35,41 +33,19 @@ namespace Apewer.Source
return Fill(query.Table, model);
}
/// <summary>获取指定列的所有值,无效值不加入结果。</summary>
public static T[] Column<T>(this IQuery query, Func<int, T> 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;
}
/// <summary>将 Query 的行,填充到模型实体。</summary>
/// <remarks>填充失败时返回 NULL 值。</remarks>
/// <exception cref="Exception"></exception>
public static object FillRow(IQuery query, int rowIndex, Type model, TableStructure structure) => FillRow(query?.Table, rowIndex, model, structure);
/// <summary>将 Query 的行,填充到模型实体。</summary>
/// <remarks>填充失败时返回 NULL 值。</remarks>
/// <exception cref="Exception"></exception>
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<DateTime>))) setter.Invoke(record, new object[] { new Nullable<DateTime>((DateTime)value) });
else if (pt.Equals(typeof(Nullable<bool>))) setter.Invoke(record, new object[] { new Nullable<bool>(Boolean(value)) });
else if (pt.Equals(typeof(Nullable<byte>))) setter.Invoke(record, new object[] { new Nullable<byte>(Byte(value)) });
else if (pt.Equals(typeof(Nullable<sbyte>))) setter.Invoke(record, new object[] { new Nullable<sbyte>(SByte(value)) });
else if (pt.Equals(typeof(Nullable<short>))) setter.Invoke(record, new object[] { new Nullable<short>(Int16(value)) });
else if (pt.Equals(typeof(Nullable<ushort>))) setter.Invoke(record, new object[] { new Nullable<int>(UInt16(value)) });
else if (pt.Equals(typeof(Nullable<int>))) setter.Invoke(record, new object[] { new Nullable<int>(Int32(value)) });
else if (pt.Equals(typeof(Nullable<uint>))) setter.Invoke(record, new object[] { new Nullable<uint>(UInt32(value)) });
else if (pt.Equals(typeof(Nullable<long>))) setter.Invoke(record, new object[] { new Nullable<long>(Int64(value)) });
else if (pt.Equals(typeof(Nullable<ulong>))) setter.Invoke(record, new object[] { new Nullable<ulong>(UInt64(value)) });
else if (pt.Equals(typeof(Nullable<float>))) setter.Invoke(record, new object[] { new Nullable<float>(Single(value)) });
else if (pt.Equals(typeof(Nullable<double>))) setter.Invoke(record, new object[] { new Nullable<double>(Double(value)) });
else if (pt.Equals(typeof(Nullable<decimal>))) setter.Invoke(record, new object[] { new Nullable<decimal>(Decimal(value)) });
else if (pt.Equals(typeof(Nullable<bool>))) setter.Invoke(record, new object[] { new Nullable<bool>(NumberUtility.Boolean(value)) });
else if (pt.Equals(typeof(Nullable<byte>))) setter.Invoke(record, new object[] { new Nullable<byte>(NumberUtility.Byte(value)) });
else if (pt.Equals(typeof(Nullable<sbyte>))) setter.Invoke(record, new object[] { new Nullable<sbyte>(NumberUtility.SByte(value)) });
else if (pt.Equals(typeof(Nullable<short>))) setter.Invoke(record, new object[] { new Nullable<short>(NumberUtility.Int16(value)) });
else if (pt.Equals(typeof(Nullable<ushort>))) setter.Invoke(record, new object[] { new Nullable<int>(NumberUtility.UInt16(value)) });
else if (pt.Equals(typeof(Nullable<int>))) setter.Invoke(record, new object[] { new Nullable<int>(NumberUtility.Int32(value)) });
else if (pt.Equals(typeof(Nullable<uint>))) setter.Invoke(record, new object[] { new Nullable<uint>(NumberUtility.UInt32(value)) });
else if (pt.Equals(typeof(Nullable<long>))) setter.Invoke(record, new object[] { new Nullable<long>(NumberUtility.Int64(value)) });
else if (pt.Equals(typeof(Nullable<ulong>))) setter.Invoke(record, new object[] { new Nullable<ulong>(NumberUtility.UInt64(value)) });
else if (pt.Equals(typeof(Nullable<float>))) setter.Invoke(record, new object[] { new Nullable<float>(NumberUtility.Single(value)) });
else if (pt.Equals(typeof(Nullable<double>))) setter.Invoke(record, new object[] { new Nullable<double>(NumberUtility.Double(value)) });
else if (pt.Equals(typeof(Nullable<decimal>))) setter.Invoke(record, new object[] { new Nullable<decimal>(NumberUtility.Decimal(value)) });
#endif
else
@ -163,6 +141,176 @@ namespace Apewer.Source
return false;
}
/// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
/// <param name="table">将要读取的表。</param>
/// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
/// <returns>由指定类型组成的数组。</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public static T[] Fill<T>(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;
}
/// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
/// <param name="table">将要读取的表。</param>
/// <param name="model">要填充的目标类型,必须是可实例化的引用类型。</param>
/// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
/// <returns>由指定类型组成的数组。</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
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<DateTime>))) prop.SetValue(record, new Nullable<DateTime>((DateTime)value), null);
else if (pt.Equals(typeof(Nullable<bool>))) prop.SetValue(record, new Nullable<bool>(NumberUtility.Boolean(value)), null);
else if (pt.Equals(typeof(Nullable<byte>))) prop.SetValue(record, new Nullable<byte>(NumberUtility.Byte(value)), null);
else if (pt.Equals(typeof(Nullable<sbyte>))) prop.SetValue(record, new Nullable<sbyte>(NumberUtility.SByte(value)), null);
else if (pt.Equals(typeof(Nullable<short>))) prop.SetValue(record, new Nullable<short>(NumberUtility.Int16(value)), null);
else if (pt.Equals(typeof(Nullable<ushort>))) prop.SetValue(record, new Nullable<int>(NumberUtility.UInt16(value)), null);
else if (pt.Equals(typeof(Nullable<int>))) prop.SetValue(record, new Nullable<int>(NumberUtility.Int32(value)), null);
else if (pt.Equals(typeof(Nullable<uint>))) prop.SetValue(record, new Nullable<uint>(NumberUtility.UInt32(value)), null);
else if (pt.Equals(typeof(Nullable<long>))) prop.SetValue(record, new Nullable<long>(NumberUtility.Int64(value)), null);
else if (pt.Equals(typeof(Nullable<ulong>))) prop.SetValue(record, new Nullable<ulong>(NumberUtility.UInt64(value)), null);
else if (pt.Equals(typeof(Nullable<float>))) prop.SetValue(record, new Nullable<float>(NumberUtility.Single(value)), null);
else if (pt.Equals(typeof(Nullable<double>))) prop.SetValue(record, new Nullable<double>(NumberUtility.Double(value)), null);
else if (pt.Equals(typeof(Nullable<decimal>))) prop.SetValue(record, new Nullable<decimal>(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
/// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
/// <param name="table">将要读取的表。</param>
/// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
/// <returns>由指定类型组成的数组。</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public static T[] Fill<T>(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;
}
/// <summary>解析 DataTable,填充没行到到指定的类型中,形成数组。</summary>
/// <param name="table">将要读取的表。</param>
/// <param name="model">要填充的目标类型,必须是可实例化的引用类型。</param>
/// <param name="compatible">当类型不同时,尝试转换以兼容。</param>
/// <returns>由指定类型组成的数组。</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
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<DateTime>))) prop.SetValue(record, new Nullable<DateTime>((DateTime)value), null);
else if (pt.Equals(typeof(Nullable<bool>))) prop.SetValue(record, new Nullable<bool>(Boolean(value)), null);
else if (pt.Equals(typeof(Nullable<byte>))) prop.SetValue(record, new Nullable<byte>(Byte(value)), null);
else if (pt.Equals(typeof(Nullable<sbyte>))) prop.SetValue(record, new Nullable<sbyte>(SByte(value)), null);
else if (pt.Equals(typeof(Nullable<short>))) prop.SetValue(record, new Nullable<short>(Int16(value)), null);
else if (pt.Equals(typeof(Nullable<ushort>))) prop.SetValue(record, new Nullable<int>(UInt16(value)), null);
else if (pt.Equals(typeof(Nullable<int>))) prop.SetValue(record, new Nullable<int>(Int32(value)), null);
else if (pt.Equals(typeof(Nullable<uint>))) prop.SetValue(record, new Nullable<uint>(UInt32(value)), null);
else if (pt.Equals(typeof(Nullable<long>))) prop.SetValue(record, new Nullable<long>(Int64(value)), null);
else if (pt.Equals(typeof(Nullable<ulong>))) prop.SetValue(record, new Nullable<ulong>(UInt64(value)), null);
else if (pt.Equals(typeof(Nullable<float>))) prop.SetValue(record, new Nullable<float>(Single(value)), null);
else if (pt.Equals(typeof(Nullable<double>))) prop.SetValue(record, new Nullable<double>(Double(value)), null);
else if (pt.Equals(typeof(Nullable<decimal>))) prop.SetValue(record, new Nullable<decimal>(Decimal(value)), null);
else
{
try
{
prop.SetValue(record, value, null);
return true;
}
catch { }
}
return false;
}
#region 数据模型 -> DataTable
/// <summary>将多个实体元素转换为 DataTable。</summary>
/// <typeparam name="T">实体元素的类型。</typeparam>
@ -748,9 +726,13 @@ namespace Apewer.Source
return table;
}
#endregion
#region DataTable 序列化
/// <summary>转换 <see cref="System.Data.DataTable"/> 到 <see cref="ObjectSet{T}"/> 数组,每行记录为一个 ObjectSet 对象。</summary>
/// <returns>当参数 table 无效时返回 0 长度的 <see cref="ObjectSet{T}"/> 数组。</returns>
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)
/// <summary>转换为 Json 对象。</summary>
public static Json ToJson(this DataTable table, Func<DateTime, string> 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;
}
/// <summary>转换为 Json 对象。</summary>
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;
}
/// <summary>转换为 Json 对象。</summary>
public static Json ToJson(this DataRowCollection rows, Func<DateTime, object> 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;
}
/// <summary>转换为 Json 对象。</summary>
public static Json ToJson(this DataRow row, Func<DateTime, object> 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;
}
/// <summary>转换 <see cref="DataTable"/> 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。</summary>
public static string Csv(DataTable table, bool withHead = false)
{
if (table == null) return null;
@ -864,6 +943,123 @@ namespace Apewer.Source
#endregion
#region DataTable 快捷操作
/// <summary>获取默认表中指定单元格的内容。</summary>
/// <param name="table">数据表。</param>
/// <param name="rowIndex">行索引,从 0 开始。</param>
/// <param name="columnIndex">列索引,从 0 开始。</param>
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;
}
/// <summary>获取默认表中指定单元的内容。</summary>
/// <param name="table">数据表。</param>
/// <param name="rowIndex">行索引,从 0 开始。</param>
/// <param name="columnName">列名称/字段名称,此名称不区分大小写。</param>
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;
}
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Class<DateTime> DateTime(this DataTable table, int row = 0, int column = 0) => table == null ? null : ClockUtility.DateTime(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Class<DateTime> DateTime(this DataTable table, int row, string column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Int32 Int32(this DataTable table, int row = 0, int column = 0) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Int32 Int32(this DataTable table, int row, string column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Int64 Int64(this DataTable table, int row = 0, int column = 0) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Int64 Int64(this DataTable table, int row, string column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Decimal Decimal(this DataTable table, int row = 0, int column = 0) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Decimal Decimal(this DataTable table, int row, string column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>>
public static Double Double(this DataTable table, int row = 0, int column = 0) => table == null ? 0D : NumberUtility.Double(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>>
public static Double Double(this DataTable table, int row, string column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static string Text(this DataTable table, int row = 0, int column = 0) => table == null ? null : TextUtility.Text(table.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
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
/// <summary>转换 ObjectSet 数组为 dynamic 数组。</summary>
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 表达式计算
/// <summary>计算文本表达式。</summary>
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
}
}

29
Apewer/StorageUtility.cs

@ -23,6 +23,16 @@ namespace Apewer
#region NTFS 流
/// <summary>搜索文件是否在 NTFS 流中附带了锁定标记。</summary>
/// <remarks>此方法仅支持 Windows 系统。</remarks>
public static bool HasZoneIdentifier(string path)
{
var info = new FileInfo(path);
var streamPath = info.FullName + NtfsUnlocker.Postfix;
var streamExists = NtfsUnlocker.FileExists(streamPath);
return streamExists;
}
/// <summary>解锁下载的文件。</summary>
/// <remarks>此方法仅支持 Windows 系统。</remarks>
public static string DeleteZoneIdentifier(string path) => NtfsUnlocker.DeleteZoneIdentifier(path);
@ -327,6 +337,25 @@ namespace Apewer
return null;
}
/// <summary>确保指定路径存在文件,若不存在则新建。</summary>
/// <param name="path">文件路径。</param>
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;
}
}
/// <summary>创建一个空文件且不保留句柄。</summary>
/// <param name="path">文件路径,若已存在则返回失败。</param>
/// <param name="length">文件长度(字节数)。</param>

83
Apewer/TextUtility.cs

@ -9,7 +9,7 @@ namespace Apewer
{
/// <summary>文本实用工具。</summary>
public class TextUtility
public static class TextUtility
{
const string BlankChars = "  \n\r\t\f\b\a"; // 在 IsBlank 和 Trim 中视为空白的字符。
@ -36,6 +36,27 @@ namespace Apewer
/// <summary>空文本。</summary>
public const string Empty = "";
/// <summary>返回表示指定对象的字符串。</summary>
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 + ">";
}
/// <summary>字符串为空。</summary>
public static bool IsEmpty(string text) => text == null || text == Empty;
@ -111,10 +132,51 @@ namespace Apewer
}
/// <summary>合并为字符串。</summary>
public static string Merge(params object[] cells) => PrivateJoin(null, CollectionUtility.ParseParams(cells));
public static string Merge(params object[] cells) => Join(null, cells);
/// <summary>合并为字符串。</summary>
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;
}
}
/// <summary>重复指定字符,直到达到指定长度。</summary>
/// <param name="cell">要重复的字符。</param>
@ -479,6 +541,21 @@ namespace Apewer
return text.Substring(starts, length - starts - ends);
}
/// <summary>修剪字符串数组,修剪元素,并去除空字符串。</summary>
public static string[] Trim(this IEnumerable<string> strings)
{
var ab = new ArrayBuilder<string>();
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;
}
/// <summary>剪取文本内容,若指定头部为空则从原文本首部起,若指定尾部为空则至原文本末尾。</summary>
/// <returns>剪取后的内容,不包含 head 和 foot。</returns>
public static string Cut(string text, string head = null, string foot = null)

63
Apewer/Web/ApiContext.cs

@ -7,46 +7,65 @@ using System.Text;
namespace Apewer.Web
{
internal sealed class ApiContext
/// <summary>API 上下文。</summary>
public sealed class ApiContext
{
internal ApiInvoker _invoker = null;
internal ApiEntries _entries = null;
internal ApiEntry _entry = null;
internal ApiProvider _provider = null;
internal Action<ApiCatch> _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;
/// <summary>此上下文启动的时间。</summary>
public DateTime Beginning { get => _beginning; }
/// <summary>API 调用器。</summary>
public ApiInvoker Invoker { get => _invoker; }
/// <summary>API 提供程序。</summary>
public ApiProvider Provider { get => _provider; }
/// <summary>API 入口集。</summary>
public ApiEntries Entries { get => _entries; }
public ApiEntry Entry { get => _entry; }
/// <summary>API 选项。</summary>
public ApiOptions Options { get => _options; }
#endregion
public ApiProvider Provider { get => _provider; }
#region 执行过程中产生的内容
public Action<ApiCatch> Catcher { get => _catcher; }
/// <summary>API 入口。</summary>
public ApiEntry Entry { get; internal set; }
public ApiOptions Options { get => _options; }
/// <summary>API 请求。</summary>
public ApiRequest Request { get; internal set; }
/// <summary>API 响应。</summary>
public ApiResponse Response { get; internal set; }
public Stopwatch Stopwatch { get => _stopwatch; }
/// <summary>API 控制器实例。</summary>
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() { }
}
}

17
Apewer/Web/ApiException.cs

@ -9,7 +9,22 @@ namespace Apewer.Web
public sealed class ApiException : Exception
{
internal ApiException(string message) { }
string _status = null;
/// <summary>表示 API 状态。</summary>
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;
}
}

2
Apewer/Web/ApiFunction.cs

@ -57,7 +57,7 @@ namespace Apewer.Web
psJson = new Class<Json>(ps);
}
}
if (psJson.HasValue) json.SetProperty("parameters", psJson.Value);
if (psJson) json.SetProperty("parameters", psJson.Value);
}
return json;
}

10
Apewer/Web/ApiInvoker.cs

@ -35,6 +35,9 @@ namespace Apewer.Web
/// <summary>异常捕获程序。</summary>
public Action<ApiCatch> Catcher { get; set; }
/// <summary>输出前的检查。</summary>
public ApiPreOutput PreOutput { get; set; }
/// <summary>执行初始化程序,每个 ApiInvoker 实例仅执行一次初始化。</summary>
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();
}

18
Apewer/Web/ApiMiddleware.cs

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

3
Apewer/Web/ApiOptions.cs

@ -46,6 +46,9 @@ namespace Apewer.Web
/// <remarks>默认值:-1,不使用 ApiOptions 限制。</remarks>
public long MaxRequestBody { get; set; } = -1;
/// <summary>输出前的检查。</summary>
public ApiPreOutput PreOutput { get; set; }
/// <summary>在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。</summary>
/// <remarks>默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。</remarks>
public bool UpgradeHttps { get; set; } = false;

12
Apewer/Web/ApiPreOutput.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>输出前的检查。</summary>
/// <returns>True: 继续执行输出。<br />False: 终止输出。</returns>
public delegate bool ApiPreOutput(ApiContext context);
}

203
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<ApiCatch> 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;
}
/// <summary>执行处理程序,返回错误信息。</summary>
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");

7
Apewer/Web/ApiResponse.cs

@ -73,6 +73,13 @@ namespace Apewer.Web
#endregion
#region Function
/// <summary>设置输出前的检查。</summary>
public ApiPreOutput PreOutput { get; set; }
#endregion
}
}

92
Apewer/Web/ApiServiceDescriptor.cs

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

23
Apewer/Web/ApiServiceLifetime.cs

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

45
Apewer/Web/ApiUtility.cs

@ -763,6 +763,51 @@ namespace Apewer.Web
response.StopReturn = true;
}
/// <summary>生成 Response 的 Json 实例。</summary>
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

53
Apewer/_Extensions.cs

@ -124,25 +124,25 @@ public static class Extensions
public static string Camel(this string @this) => TextUtility.Camel(@this);
/// <summary>转换为 Boolean 值。</summary>
public static bool Boolean(this object @this) => NumberUtility.Boolean(@this);
public static bool Boolean(this string @this) => NumberUtility.Boolean(@this);
/// <summary>转换为 Byte 值。</summary>
public static byte Byte(this object @this) => NumberUtility.Byte(@this);
public static byte Byte(this string @this) => NumberUtility.Byte(@this);
/// <summary>转换为 Int32 值。</summary>
public static int Int32(this object @this) => NumberUtility.Int32(@this);
public static int Int32(this string @this) => NumberUtility.Int32(@this);
/// <summary>转换为 Int64 值。</summary>
public static long Int64(this object @this) => NumberUtility.Int64(@this);
public static long Int64(this string @this) => NumberUtility.Int64(@this);
/// <summary>转换为 Decimal 值。</summary>
public static decimal Decimal(this object @this) => NumberUtility.Decimal(@this);
public static decimal Decimal(this string @this) => NumberUtility.Decimal(@this);
/// <summary>转换为单精度浮点值。</summary>
public static float Float(this object @this) => NumberUtility.Float(@this);
public static float Float(this string @this) => NumberUtility.Float(@this);
/// <summary>转换为双精度浮点值。</summary>
public static double Double(this object @this) => NumberUtility.Double(@this);
public static double Double(this string @this) => NumberUtility.Double(@this);
/// <summary>将文本转换为字节数组,默认使用 UTF-8。</summary>
public static byte[] Bytes(this string @this, Encoding encoding = null) => TextUtility.Bytes(@this, encoding);
@ -280,6 +280,30 @@ public static class Extensions
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static DateTime DateTime(this long stamp, DateTimeKind kind = DateTimeKind.Unspecified, bool throwException = true) => ClockUtility.FromStamp(stamp, kind, throwException);
#region Nullable
/// <summary>转换为易于阅读的文本。</summary>
/// <remarks>格式:1970-01-01 00:00:00.000</remarks>
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);
/// <summary>转换为紧凑的文本。</summary>
public static string Compact(this DateTime? @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds);
/// <summary>获取毫秒时间戳。</summary>
public static long Stamp(this DateTime? @this, bool byMilliseconds = true) => @this == null ? default : ClockUtility.Stamp(@this.Value, byMilliseconds);
/// <summary>转换为易于阅读的文本。</summary>
/// <remarks>格式:1970-01-01 00:00:00.000</remarks>
public static string Lucid(this Class<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);
/// <summary>转换为紧凑的文本。</summary>
public static string Compact(this Class<DateTime> @this, bool date = true, bool time = true, bool seconds = true, bool milliseconds = true) => @this == null ? default : ClockUtility.Compact(@this.Value, date, time, seconds, milliseconds);
/// <summary>获取毫秒时间戳。</summary>
public static long Stamp(this Class<DateTime> @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
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
public static Class<DateTime> DateTime(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : Query.DateTime(@this.Value(row, column));
public static Class<DateTime> DateTime(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : ClockUtility.DateTime(@this.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static Class<DateTime> DateTime(this IQuery @this, int row, string column) => @this == null ? null : Query.DateTime(@this.Value(row, column));
public static Class<DateTime> DateTime(this IQuery @this, int row, string column) => @this == null ? null : ClockUtility.DateTime(@this.Value(row, column));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
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));
/// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
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));
/// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
public static string Text(this IQuery @this, int row, string column) => @this == null ? null : Query.Text(@this.Value(row, column));
/// <summary>转换 <see cref="System.Data.DataTable"/> 到 <see cref="ObjectSet{T}"/> 数组,每行记录为一个 ObjectSet 对象。</summary>
/// <returns>当参数 table 无效时返回 0 长度的 <see cref="ObjectSet{T}"/> 数组。</returns>
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));
/// <summary>转换 <see cref="System.Data.DataTable"/> 到 <see cref="ObjectSet{T}"/> 数组,每行记录为一个 ObjectSet 对象。</summary>
/// <returns>当参数 table 无效时返回 0 长度的 <see cref="ObjectSet{T}"/> 数组。</returns>
public static ObjectSet[] ObjectSet(this IQuery @this) => @this == null ? null : SourceUtility.ObjectSet(@this.Table);
/// <summary>转换 <see cref="DataTable"/> 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。</summary>
public static string Csv(this DataTable table, bool withHead = false) => SourceUtility.Csv(table, withHead);
/// <summary>转换 <see cref="DataTable"/> 为 CSV 文本,不存在表时返回 NULL 值。可指定是否包含表头。</summary>
public static string Csv(this IQuery query, bool withHead = false) => query == null ? null : SourceUtility.Csv(query.Table, withHead);

33
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<DateTime> 类型;
- 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<T> 去除了无用的 HasValue 属性
### 6.7.2
- 问题修正
- 修正了 SQLite 的构造函数中的 path 参数;

Loading…
Cancel
Save