改版紀錄
- 新增了一個實驗機制,當
SQL
class 啟用EnableDBAlwaysOn
設定時,QuickDynamicQuery
與QuickQuery
兩個方法會偵測查詢結果的資料筆數,如果為 0 筆時,此時若為SQLReadOnly=true
模式,則會自動重新建立一次 SQL 連線,並且改用SQLReadOnly=false
重新執行一次查詢。此功能是為了避免讀寫分離架構啟用時,寫入的資料在 primary db 尚未同步到 secondary db 上的錯誤。由於此方式過於粗糙因此目前僅為歸納為實驗性質,後續仍待評估更好的做法。
包含了
2.4.9
~2.4.12
- 新增了
SQL
class 支援讀寫分離的 DB 連線參數設定
- 新增成員
EnableDBAlwaysOn
: 是否啟用 DB Always On 才有的ApplicationIntent=ReadOnly
設定,預設為false
。也可於 Config 檔案中指定EnableDBAlwaysOn
為True
。 - 新增成員
SQLReadOnly
: 必須設置EnableDBAlwaysOn
設置為true
時,此設定才會生效 (否則為空操作)。 如果此值為true
,且EnableDBAlwaysOn
也為true
時,會自動將資料庫連線字串中的ApplicationIntent
設置為ReadOny
。此數值預設為false
。 - 請注意 Config 檔案中
EnableDBAlwaysOn
指定為False
或不指定時, 連線字串將強制設置ApplicationIntent=ReadWrite
。 - 詳細使用方式請參閱:使用範例
- 新增了
Config
class 中的 2 個新功能
Config.Delete(key)
:刪除指定的appSettings
中的參數Config.DeleteConnectionString(key)
刪除指定的connectionStrings
資料庫連線字串- 詳細使用方式請參閱:使用範例
- 新增了
Config
class 中的 3 個新功能 (以下功能皆可在 Config 檔案被加密的狀況下正常運作)
Config.SetOrAdd(key, val)
:設定既有的或新增全新的appSettings
中的參數 (已存在則會修改,不存在則會新增)Config.SetOrAddConnectionString(key, val, providerName)
:設定既有的或新增全新的connectionStrings
資料庫連線字串 (已存在則會修改,不存在則會新增)Config.GetConnectionStrings()
:取得所有資料庫連線字串設定,如果無法取得則回傳null
- 詳細使用方式請參閱:使用範例
- 將
Crypto.Md5()
方法中原先依賴使用System.Security.Cryptography.MD5
的部分移除,並改為自行實作 MD5 雜湊的方式提供
- 新增了
SQL
class 中的一個新功能SQLDBReplace()
,提供了無須變更任何 SQL 語法即可自動變更 SQL 語法中 DB Name 部分格式的功能 詳情說明
- 新增了
SQL
class 中的一個新方法QuickBulkCopy()
,允許一次性大量寫入資料,避免逐筆寫入的效能問題 使用範例
MyLog
支援自訂義 Log 檔案副檔名 (原本只能強制.txt
) 使用範例RegExp
支援自訂正規表達式選項 (原本強制只能RegexOptions.IgnoreCase
) 使用範例SQL
優化成會根據 ConnectionString 動態填補缺少的設定,已經指定於 ConnectionString 中的設定將不會被覆蓋 (例如 Connect Timeout) 使用範例Crypto
支援可指定自訂義編碼 (原本只能Encoding.ASCII
) 使用範例
- 新增支援
Mailer
中支援 Password 參數如果給null
則可跳過驗證 ImplicitMailer
已經支援
- 在
ImplicitMailer
與Mailer
中新增AddAttachments()
方法,可以新增郵件附件,並取得cid
- 新增成員
AttachmentsList
可以取得使用AddAttachments()
新增的附件列表
以下是附加檔案,並取得 Content ID 的範例:
Mailer m = new Mailer(SMTP_HOST, MAIL_ACT, MAIL_PWD, MAIL_PORT, MAIL_SSL);
string cid = m.AddAttachments(@"D:\Downloads\user.png");
string BODY = $"<p>Image here: <img src=\"cid:{cid}\" ></p>";
// 附件列表
var atts = m.AttachmentsList;
- 新增
ImplicitMailer
與Mailer
的Send()
方法中新增attchments
參數,允許傳入實體檔案路徑作為郵件附加檔案,一起寄出 - 新增 Config
TLS12
如果設為true
則全系統使用Fetch
功能時,會強制呼叫 TLS1.2 協議,呼叫方式如下:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
- 修正因為版本
2.2.5
支援qs
為null
時導致 URL 中已經指定的 query string 被覆蓋成null
的問題所產生的副作用錯誤
- 新增全新功能
ImplicitMailer
類別,底層採用 Aegis Implicit Mail (AIM) 套件進行寄信功能,該套件支援隱式 (Implicit) SSL 加密連線,支援 port465
的 SMTP 連線加密協議。使用方式如下:
string SMTP_HOST = "smtp.gmail.com";
string MAIL_ACT = "[email protected]";
string MAIL_PWD = "...";
int MAIL_PORT = 465;
bool MAIL_SSL = true; // 指定 false 則會採用 auto 自動適應 SSL or TLS
ImplicitMailer m = new ImplicitMailer(SMTP_HOST, MAIL_ACT, MAIL_PWD, MAIL_PORT, MAIL_SSL);
string TO = "[email protected],[email protected]";
string SUBJECT = $"This is a test mail (ZapLib.ImplicitMailer) - {DateTime.Now}";
string BODY = $"<h1>Test Mail by ZapLib ImplicitMailer</h1>";
bool result = m.Send(TO, SUBJECT, BODY); // 發送郵件
Trace.WriteLine(m.ErrMsg); // 印出寄信的細節結果
- 修改
Fetch
允許呼叫Get
Post
等方法時, 如果qs
query string 參數帶入null
,會將 URL 中已經指定的 query string 覆蓋的問題。現在指定null
將可保留 URL 中設置的 query string
- 修改 Mailer 成員允許修改 SecureSocketOptions
- 修改
SQL
Class 的Timeout
參數,現在也會同步設定到CommandTimeout
- 寄信功能 Mailer 的 Send 方法,支援一次傳送多個信箱(逗號隔開)
- 寄信功能 Mailer 的 Send 方法,新增 CC (副本) 與 BCC (密件副本)
- 寄信功能 Mailer 改為使用 MailKit 支援 TLS1.2 更安全的加密協議 (介面與之前完全相同,可向下相容)
- 可以設定 Config 參數
MyLog
,來指定 Log 寫入的位置 (如果沒有設置則會採用Storage
) - 新增全新 Config 參數
LogExecTime
,當設為true
時,Log 將會自動紀錄 SQL 的執行時間 - 新增全新類別
LogExecTime
,可以記錄程式的執行時間 (Config 參數LogExecTime
設為true
時才會生效)
LogExecTime lxt = new LogExecTime("Unit Test");
Thread.Sleep(1500);
lxt.Log();
Log
[11:40:19] [Log Exec Time] Unit Test
Takes 1.514 second
- 修復 ExtApiHelper 中用到 Stream 物件的程式,資源不正常釋放的問題
- 將 ExtApiHelper 的 Response 與 Request 改為 public 存取
- 新增 ZipHelper 快速 Zip 壓縮的類別函式庫
- 新增 MyLog.Read(page) 方法,能以翻頁的方式讀取 Log 檔案
- 新增 Fetch.Patch 方法
- 修改 MyLog 建構子,以利向下相容
- 允許設置 ExtApiHelper 的 Encoding 修改後,所有 Response 都可以套用
- 修改所有 ExtApiHelper 中用到 Stream 物件的程式,全部加上 using 釋放資源
- jQuery 1.6.4 → 3.6.0
- Microsoft.AspNet.SignalR 2.2.3 → 2.4.3
- Microsoft.AspNet.SignalR.Core 2.2.3 → 2.4.3
- Microsoft.AspNet.SignalR.JS 2.2.3 → 2.4.3
- Microsoft.AspNet.SignalR.SystemWeb 2.2.3 → 2.4.3
- Microsoft.AspNet.WebApi.Client 5.2.4 → 5.2.7
- Microsoft.AspNet.WebApi.Core 5.2.4 → 5.2.7
- Microsoft.AspNet.WebApi.WebHost 5.2.4 → 5.2.7
- Microsoft.Owin 2.1.0 → 4.2.0
- Microsoft.Owin.Host.SystemWeb 2.1.0 → 4.2.0
- Microsoft.Owin.Security 2.1.0 → 4.2.0
- Newtonsoft.Json 11.0.2 → 12.0.1
- 新增以 Stream 作為輸入參數的 API Response 函數
GetStreamResponse
- SQL 支援類別的成員是 Nullable Enum 資料類型的 property (資料會嘗試轉換到 Enum,如果失敗則會容錯成 Enum 的預設值,不會變成 null)
Cast.To
如果嘗試轉換成 Enum 類別,則會自動容錯成呼叫 Enum.To 函數- Config 新增
Config.Refresh
方法,如果應用程式運行過程中可能會改變.config
檔案,可以自行決定何時刷新 Config 中的快取 (cache) - 新增
JXPath.GetValue
Utility 類別方法,允許指定 XPath 來取出特定dynamic
或 JSON 字串中指定路徑位置的數值 - 新增
Mirror.GetClasses
可以掃描全系統 class 取得特定 class 或取得有繼承 class 的 classes - 新增全新功能
ClassMirror
可以輸入指定的 Type 並建立實體 - 新增全新
DllLoader
Utility 類別方法,允許指定 dll 檔案路徑並建立 assembly 組件
- 增加 標註棄用說明
SQL.Exec
已標註棄用,將在下一個版本刪除 - 修正
ExtApiHelper
呼叫GetJsonBody<T>
無法正常取得資料的問題 - 修正 執行
MyLog.write
時,如果Storage
目錄不存在會造成 stackoverflow 崩潰的問題,目錄不存在時,將完全不執行 - 修正 使用
Fetch
傳入不合法的 URL 時會直接崩潰的問題,並且會自動 Log 提示 - 優化 執行
Mirror.Assign
或Cast.To
時,如果目標 Type 為Nullable<T>
將會嘗試使用T
底層類別 (under type) 重新嘗試轉換
- 修正上傳檔案參數撰寫錯誤(#32)的 bug
- 修正 query string 中文變亂碼的問題
- 增加 sql order by 白名單過濾
- 修正 ValidPlatform 少做一次內容驗證問題
- 增加 fetch 取得 header 自動轉型
- 修正 Mirror.Member 可能會噴錯的問題
- 新增 DynamicObject 動態實體物件
- 修正 ExpParam 在遇到變數名稱相似的時候 ,會取代錯誤的問題
- 修正 SQL getErrorMessage 有時候無法取得完整的 Exception 資訊的問題
- Fixed Fetch Oject Disposed Exception
- Fixed Proxy setting.
- Add transaction options in SQL
- Add get headers in ExtApiHelper
ZapLib/ExtApiHelper.cs
AddGetHeader
andGetHeader<T>
functionZapLib/Utility/Cast.cs
AddToEnum
andToEnum<T>
function
- Add New Feature JsonReader
- implement interface
ISQLParam
can custom function to run way of your process - add
SQLTypeAttribute
defined SQL output model for mapping data type - implement interface
ISQLTypeAttribute
can custom function to run way of your process ZapLib/Utility/Mirror.cs
AddGetCustomAttributes<T>
- New Feature SQL 參數擴展 ,允許使用
ExpParam<T>
以陣列當作資料,並自動生成 Prepare statement args
- beta
MyLog 從這個版本開始,將會忽略因為 Log 檔案無法寫入的錯誤 (e.g. 多執行續共寫一個 Log 檔案),並且允許全局關閉 ZapLib 內建的 Log 訊息。
Force Log
如果你仍需要在無法寫入 Log 檔案時,完整保留 Log 資訊,可以在 App.config
或是 Web.config
中添加 ForceLog
,ZapLib 將自動把資訊寫在另一個檔案中,它看起來像是這樣 原本 Log 檔名-0f8fad5b-d9cb-469f-a165-70867728950e.txt
,它會有助於你進行 debug 作業
<add key="ForceLog" value="True"/>
Silent Mode
如果你需要全局關閉 ZapLib 內建的 Log 訊息,可以在 App.config
或是 Web.config
中添加 SilentMode
,一旦設置了這個選項,ZapLib 將關閉所有內建的 Log 訊息,但您還是可以呼叫 MyLog
功能進行紀錄,不會有所影響。
<add key="SilentMode" value="True"/>
更新指南
- 如果你需要在 Log 檔無法寫入強破紀錄訊息,可以開啟 Force Log 的功能
- 如果你需要關閉所有 ZapLib 的內建 log,可以開啟 Silent Mode 的功能
- 如果你沒有以上需求,可以無需做任何修改,直接更新
Form Data
Fetch 預設會以 json 編碼傳送資料,如果需要傳統的 From 編碼格式,可以在 contentType
中指定
Fetch f = new Fetch("https://httpbin.org/post");
// 預設
f.contentType = "application/json";
// 使用 URL 編碼傳送資料 x-www-form-urlencoded
f.contentType = "application/x-www-form-urlencoded";
// 使用傳統 Form 傳送資料
f.contentType = "multipart/form-data"
Fetch 現在可以傳送 Dictionary
物件
示範:
Dictionary<string, string> data = new Dictionary<string, string>();
data.Add("test", "123");
Fetch f = new Fetch("https://httpbin.org/post");
f.contentType = "application/x-www-form-urlencoded";
f.post(data);
只能是
Dictionary<string, string>
不可為其他型態
使用連線字串連線
SQL API 可以使用自訂的 Connection String 進行連線
SQL db = new SQL("Data Source=(LocalDb)\MSSQLLocalDB;Initial ..."); // 使用自訂 Connection String 連線
getConnection()
並且新增了 getConnection()
方法取代原先只能用 connet()
方法取得目前連線物件的方式
SQL db = new SQL();
db.connect();
SqlConnection myConn = db.getConnection();
quickDynamicQuery()
與 dynamicFetch()
新增了動態快速查詢 API,將返回 dynamic[]
型態的資料,查詢失敗一樣回傳 null
SQL db = new SQL();
dynamic[] data = db.quickDynamicQuery("select * from entity");
for(int i = 0; i < data.Length; i++)
{
Trace.WriteLine((string)data[i].cname);
}
如果需要手動連線,進行細節操作
SQL db = new SQL();
db.connet();
SqlDataReader stmt = db.query(sql);
dynamic[] data = dynamicFetch(stmt);
stmt.Close();
db.close();
quickDynamicQuery()
與 dynamicExec()
新增了快速執行 stored procedure,將返回 dynamic
型態的資料,查詢失敗一樣回傳 null
SQL db = new SQL();
var input_para = new
{
act = "admin",
passportcode = "1234567890"
};
var output_para = new
{
res = SqlDbType.Int
};
dynamic data = db.quickDynamicExec("xp_checklogin", input_para, output_para);
Trace.WriteLine((int)data.res);
如果需要手動連線,進行細節操作
SQL db = new SQL();
db.connet();
dynamic obj = dynamicExec(sql, input_para, output_para);
db.close();
新增了全新的平台驗證功能於 using ZapLib.Security
Controller 自動驗證
提供 WebApi Controller 新的標籤 [ValidPlatform]
用於驗證是否為信任的請求,使用方式如下:
// 在某一個 Controller 的 Action 中
[ValidPlatform]
[Route("test")]
public HttpResponseMessage test_file()
{
// 如果驗證通過才會進入這個 Action
}
Fetch
附加驗證
如果使用 Fetch
呼叫具有 [ValidPlatform]
驗證的 API,可以啟用 validPlatform
改為 true
便會自動附加驗證的資訊
Fetch2 f = new Fetch2("https://httpbin.org/post");
// 附加平台驗證資訊
f.validPlatform = true;
object res = f.post<object>(new { data = "123", result = true, number = 123 });
系統金鑰
與 上帝鑰匙
要通過平台驗證,兩個平台必須要有相同的 系統金鑰
,如果要改變預設的 系統金鑰
,可以在系統進入點添增設定
WebApiConfig.cs
ZapLib.Security.Const.Key = "新的金鑰";
為了開發人員方便,驗證留有一個後門 上帝鑰匙
可以直接通過驗證,預設為 nvOcQMfERrASHCIuE797
,也可以在系統進入點修改它
WebApiConfig.cs
ZapLib.Security.Const.GodKey = "上帝鑰匙";
驗證方式與規格
平台驗證實作了 DES 方法,但是稍作改良,請求端會再 HTTP Header 附加 3 個欄位資料
key | description |
---|---|
Channel-Signature |
將傳送的資料以 MD5 加密後的字串,作為請求的簽章 |
Channel-Iv |
一組每次都會亂數產生的加密種子 |
Channel-Authorization |
以 Channel-Signature + Channel-Iv + 系統金鑰 使用 DES 雜湊後的結果 |
如果在 Controller 附加了 [ValidPlatform]
則會在進入 Action 之前,先將 Channel-Signature
+ Channel-Iv
+ 系統金鑰
在進行一次 DES 雜湊,比對 Channel-Authorization
數值是否相同,如果相同就會驗證通過。
驗證不通過時,會直接回傳 401 Unauthorized
未授權回應。
新增了 ExtApiHelper.getStreamResponse
用從伺服器中抓取檔案並以串流方式回應,使用範例如下:
getStreamResponse
arg | type | required | description |
---|---|---|---|
file | string 或 byte[] | Y | 檔案實體路徑 或 存放於記憶體中的檔案 |
name | string | N | 檔案名稱,如果沒有給予,則會以亂數命名 |
type | string | N | 檔案 MIME TYPE,如果沒有給予,則會以預設 application/octet-stream |
disposition | string | N | 串流方式,預設為 attachment 會直接下載檔案,如果要在瀏覽器中顯示,例如圖片,可以設定成 inline |
注意:
disposition
如果設置為inline
,type
也必須設定成可以支援直接顯示的類型 e.g.image/jpeg
,image/png
...
// 在某一個 Controller 的 Action 中
ExtApiHelper api = new ExtApiHelper(this);
// 將會直接下載,並以亂數命名檔案名稱
return api.getStreamResponse(@"D:\a.txt");
新增了 ExtApiHelper
擴充功能 addIdentityPaging(ref string sql, string orderby = "since desc", string idcolumn = null, string nextId = null)
可以使用 ID 來做為分頁基準,細節可以參閱 Identity Paging
中的描述,使用範例如下:
- 第一頁(第一次抓取),
nextId
為null
:
// 在某一個 Controller 的 Action 中
ExtApiHelper api = new ExtApiHelper(this);
string sql = "select * from entity where eid>10";
string nextId = null;
api.addIdentityPaging(ref sql, "eid desc", "eid", nextId);
// 此時 sql 是:select top(51) * from entity where eid>2 order by eid desc
- 此時請注意,API 預設抓取 n 筆資料(在
Web.config
中設定APIDataLimit
),但addIdentityPaging
會抓取 n+1 筆資料,目的是為了判斷是否還能翻下一頁,請自行刪除最後一筆資料並取得最後一筆資料的 ID,作為下一次翻頁的基準
SQL db = new SQL();
ModelEntity[] data = db.quickQuery<ModelEntity>(sql);
if(data.Length > int.Parse(Config.get("APIDataLimit")))
{
// 取出最後一筆的 ID
string nextId = data[data.Length - 1].eid.ToString();
// 刪除最後一筆資料
Array.Resize(ref data, data.Length - 1);
}
- 第二頁(第二次抓取),
nextId
為100
:
string sql = "select * from entity where eid>10";
api.addIdentityPaging(ref sql, "eid desc", "eid", nextId);
// 此時 sql 是:with tb as(select ROW_NUMBER() over(order by eid desc) as _seq,* from entity where eid>2) select top(51) * from tb where _seq>=(select _seq from tb where eid='100') order by eid desc
新增了 ApiController
擴充了 SignalR 功能的 ApiControllerSignalR
類別,使用範例如下:
首先在 Web API 專案根目錄下新增 Startup.cs
並撰寫以下程式碼 (這裡的設定可以依照需求調整)
public class Startup
{
public void Configuration(IAppBuilder app)
{
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
app.MapSignalR(hubConfiguration);
}
}
接下來在根目錄下的 Global.asax
中加入以下程式碼 (這裡的 TimeSpan.FromSeconds(10)
可依照需求調整)
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(10);
接下來新增一個 Hubs
目錄,並在其中新增一個 MsgHub.cs
類別
[HubName("messaging")]
public class MsgHub : Hub
{
// write something...
}
最後,開啟欲使用 SignalR 的 Controller,將原先的 ApiController
改為 ZapLib 提供的 ApiControllerSignalR
,如此一來便能使用下方展示的擴充功能
___Controller.cs
[RoutePrefix("api")]
public class MsgController : ApiControllerSignalR<MsgHub>
{
[HttpPost]
[Route("msg/send")]
public HttpResponseMessage sendMsg([FromBody]ModelMsg msg)
{
// 廣播給某個對象
Hub.Clients.Client( id ).pushMsg("...");
// 檢查特定 ID 連線狀態
bool isAlive = IsConnectionIdAlive("Connection ID");
string[] connectionIds = new string[]{ };
// 廣播給某群人
Hub.Clients.Clients(connectionIds).receiveMsg("...");
// 解析一群 ID 的連線狀態
IList<string> Alive, Dead;
ResolveConnectionIds(connectionIds, out Alive, out Dead);
foreach(var id in Dead)
{
// 無效的連線 ID
}
}
}
其中
Hub
是GlobalHost.ConnectionManager.GetHubContext
取出的HubContext
可以直接使用原生的功能 另外ResolveConnectionIds(IList<string> connectionIds, out IList<string> Alive, out IList<string> Dead)
與IsConnectionIdAlive(string connectionId)
提供的功能實作原理,可以參閱技術說明
- 可以取得 SQL 最後錯誤資訊,不一定要寫入 Log 觀察,使用範例如下:
SQL db = new SQL("localhost", "dbname", "account", "password");
object[] o = db.quickQuery<object>("select * from class");
string error = db.getErrorMessage(); // 輸出錯誤資訊
- 修復
ExtApiHelper.getAttachemntResponse
無法指定副檔名的問題,,使用範例如下:
ExtApiHelper api = new ExtApiHelper(this);
api.getAttachemntResponse("內容","file_name.txt"); // 可以直接指定副檔名,如果沒有指定則無附檔名
- 新增 MyLog 可以指定儲存位置與 Log 的檔案名稱
MyLog log = new MyLog();
log.path = "D:\\Log"; // 指定儲存路徑,預設為抓取 config 中的 Storage 設定,如果都沒有指定則不會進行 Log 寫入
log.name = "mylog"; // 指定 log 檔案名稱,預設為 yyyyMMdd
log.write("wewfewfewfewfewf");
log.write("safawfqafw");
- 新增 Fetch 可以取得 Response 的 Header 資訊 (如果無法取得 Header 則會回傳 NULL)
Fetch fetch = new Fetch("http://localhost");
fetch.get(null);
string header_value = fetch.getResponseHeader("key"); // 取得指定 Header
WebHeaderCollection all_headers = fetch.getResponseHeaders(); // 取得全部 Headers
- 新增了 SQL BCP 的功能,可以快速寫入大量資料,使用範例如下:
// 建立 DataTable 物件
var dt = new DataTable();
dt.Columns.Add("words", typeof(string));
// 將資料塞進 DataTable 中
foreach (string word in words)
{
var row = dt.NewRow();
row["words"] = new_word;
dt.Rows.Add(row);
}
// 全部一次性寫入
SQL db = new SQL();
result = db.quickBulkCopy(dt, "dbo.UD");