Quantcast
Channel: Darkthread
Viewing all 428 articles
Browse latest View live

JavaScript 將輸入欄位內容複製到剪貼簿

$
0
0

部落格原本用的程式碼顏色標示(Syntax Highlight)套件是十年前的產物,早就跟不上程式語言演進腳步,上篇文章起網站悄悄改版,改用支援 176 種語法及 76 種樣式並持續更新的 highlight.js。(列入評估的另一選項 prism.js也很出色,頗難抉擇,最後選定 Github 觀注度較高的 highlight.js。延伸閱讀:Top 5 : Best code syntax highlighter javascript plugins | Our Code World)

highlight.js 未內建複製到剪貼簿功能,上網逛了一圈,大家一致推崇 clipboard.js。試用心得頗佳,輕鬆上手但威力強大,採純 JavaScript(不需 Flash,IE9+ 可用),特整理筆記分享推薦。

註: 為防大家沒注意到我有加上「點選圖示複製程式碼」功能,錄了一段示範:(圖示的大小及位置會太低調嗎? XD)

在此示範整合 clipboard.js 兩種最常見做法:另設複製按鈕、點擊欄位本身複製。使用方法很簡單,網頁引用 clipboard.js,在要觸發複製動作的元素(按鈕或是<input type="text">或<textarea>本身)加上 data-clipboard-target="#selector" 指向複製內容的來源。使用 new ClipboardJS(selector) 為觸發動作元素加上複製功能,selector 是複製觸發元件選擇器,為求省事此處用 [data-clipboard-target],實務上多半會賦與它們共用的 class 樣式以利快速選取。除此之外,還有將複製文字寫成屬性(data-clipboard-text)、改成剪下到剪貼簿(data-clipboard-action="cut")、掛載事件等進階用法,詳情可參考官網首頁,網站介紹很淺顯易懂,此處就不多贅述。

完整程式範例如下:Live Demo

<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>JavaScript 複製到剪貼簿</title><script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"></script><style>
    body { font-size: 10pt; }
    dd,dt { padding: 3px; }</style></head><body><dl><dt>測試區</dt><dd><textarea style="width: 250px;height: 50px;"></textarea></dd>    <dt>按鈕複製內容</dt><dd><input value="Hello, " id="text1"><button data-clipboard-target="#text1">複製</button></dd><dt>點搫複製</dt><dd><input value="World!" id="text2" data-clipboard-target="#text2"></dd></dl><script>
    //透過selector標註所有複製觸發元素
    new ClipboardJS("[data-clipboard-target]");</script></body></html>

如此,點複製鈕甚至點擊欄位本身就能將欄位內容傳入剪貼簿,是不是簡單又方便呢?又學到一招!


HttpUtility.ParseQueryString 還原字串中文編碼問題

$
0
0

在 .NET 裡要解析 URL 參數字串(QueryString,例如: a=1234&b=ABCD),自己拆字串就遜掉了,呼叫 HttpUtility.ParseQueryString()才是王道,這是我很多年前就學到的知識。

最近再有個新發現,ParseQueryString() 所傳回的結果表面上是個 NameValueCollection,但骨子裡則是內部型別 – HttpValueCollection,它有個特異功能,ToString() 被覆寫成可將 Name/Value 再組合還原成 QueryString,所以我們可以用它解析 QueryString,增刪修改參數再 ToString() 轉回 QueryString,十分方便。

不過,試著試著踩到一顆地雷,它的 ToString() 處理中文編碼有問題:

static void TestParseQueryString()
{
	var urlQuery = "a=123&b=%E4%B8%AD%E6%96%87";
	var collection = HttpUtility.ParseQueryString(urlQuery);
	Console.WriteLine($"Query: {urlQuery}");
	Console.WriteLine($"ParseQueryString: a={collection["a"]},b={collection["b"]}");
	Console.WriteLine($"ToString: {collection.ToString()}");
}

實測解析內含 UrlEncode 中文參數再還原,可發現 HttpValueCollection.ToString() 傳回的不是 UTF-8 編碼 UrlEncode,而是過時 %uxxxx 格式 ! (延伸閱讀:【茶包射手日記】勿用 UrlEncodeUnicode 與 escape )

HttpValueCollection 原始碼也證實這點,註解提到是為了向前相容才繼續使用被標為過時(Obsolete)的 UrlEncodeUnicode方法,而程式碼埋了一段偵測 AppSettings.DontUsePercentUUrlEncoding 設定改用 UrlEncode 的邏輯:

// HttpValueCollection used to call UrlEncodeUnicode in its ToString method, so we should continue to
// do so for back-compat. The result of ToString is not used to make a security decision, so this
// code path is "safe".
internal static string UrlEncodeForToString(string input) {
	if (AppSettings.DontUsePercentUUrlEncoding) {
		// DevDiv #762975: <form action> and other similar URLs are mangled since we use non-standard %uXXXX encoding.
		// We need to use standard UTF8 encoding for modern browsers to understand the URLs.
		return HttpUtility.UrlEncode(input);
	}
	else {
#pragma warning disable 618 // [Obsolete]
		return HttpUtility.UrlEncodeUnicode(input);
#pragma warning restore 618
	}
}

換言之,在 config 加入以下設定即可讓 HttpValueCollection 改用 UrlEncode:

<appSettings><add key="aspnet:DontUsePercentUUrlEncoding" value="true" /></appSettings>

實測成功!

不過,若是在共用函式或公用程式庫使用 HttpValueCollection,要求開發者修改 config 配合太擾民。故還有另一種解法,先用 UrlDecode() 解碼再用 Uri.EscapeUriString() 轉回標準 UTF-8 編碼:

 static void TestParseQueryString()
{
	var urlQuery = "a=123&b=%E4%B8%AD%E6%96%87";
	var collection = HttpUtility.ParseQueryString(urlQuery);
	Console.WriteLine($"Query: {urlQuery}");
	Console.WriteLine($"ParseQueryString: a={collection["a"]},b={collection["b"]}");
	//先UrlDecode解開再使用EscapeUriString置換
	var fixedResult = Uri.EscapeUriString(HttpUtility.UrlDecode(collection.ToString()));
	//HttpUtility.UrlDecode(collection.ToString()) => a=123&b=中文
	//Uri.EscapeUriString("a=123&b=中文") => a=123&b=%E4%B8%AD%E6%96%87
	Console.WriteLine($"ToString: {fixedResult}");
}

這樣也能修正問題,報告完畢。

Angular 1.x 技術支援何時終止?

$
0
0

近一年多來工作主軸移回後端,我在前端方面進展有限,偶爾遇到要寫 Web UI 的場合,還是要靠過去整理好的 Angular 1.x + KendoUI 共用程式庫,歷經時間與實務需求磨練過,功能完整度及穩定性良好,加上使用經驗豐富,三兩下就能拼出所需介面。雖然先前已評估好未來要轉向 Vue.js,但老鳥做專案必有私房工具箱,要補到功能齊備,磨到方便順手豈是一朝一夕的事,因此我有心理準備,現階段遇到時程吃緊不容閃失的專案,這套 Angular 1.x 工具組還是衝鋒陷陣最可靠的良伴。

只是,Angular 最新版已到 5.2,Angular 1.x 完全被淘汰是早晚的事。有繼續使用 1.x 的計劃,就得關心它的技術支援期限,當官方不再修復 Bug 及資安漏洞,使用風險上升,到時被資安或稽核單位拿刀子逼著換底層,肯定不好玩。

所以問題來了(老闆同事必問,我也常常自問):Angular 1.x 我還可以裝死撐多久官方技術支援會在何時中止?

Angular Team 在今年一月底的部落格文章( Stable AngularJS and Long Term Support – Angular Blog ) 對此給了完整說明,整理如下:

  1. AngularJS (1.x) 過去吸引了數百萬名開發者,其繼任者 Angular (2+) 推出後的成長速度是 AngularJS 的五倍,於 2017 年 10 月開發者達到 100 萬人( 以 30 天內訪問文件網站使用者推算 ),超越 AngularJS 的線上使用量。
  2. Angular Team 知道從 AngularJS 升級到 Angular 需要時間與精力,而 AngularJS 開發者也關心未來發展。
  3. 開發團隊正在著手 AngularJS 1.7 的研發,預計會於 2018/6/30 推出。
  4. 2018/7/1 起開始進入為期三年的長期支援(LTS, Long Term Support),將不再加入新功能,只修補 Bug 及安全漏洞。
  5. AngularJS 的所有 npm, bowser, cdn 資源會持續提供。
  6. 6/30 前將釋出 1.7 最新修補版(例如: 1.7.1或1.7.2),之後就不會再加入任何更動規格(Breaking Change)的功能或修正。(如果你覺得有什麼功能應該要納入 1.7,請儘早提出)
  7. LTS 只會針對以下問題進行修補:安全問題、新版瀏覽器支援問題、新版 jQuery 造成的相容問題。

【結論】

今年 6/30 推出的 1.7 將是 Angular 1 的最後版本,到 2021/6/30 為止提供三年長期支援,但只修復 Bug 與安全問題,不再加入任何新功能,至 2021/7/1 完全停止支援,還在用 Angluar 1.x 的同學可以依此時程倒數計時。

Coding4Fun - 別踩白塊兒 App 硬體外掛

$
0
0

小時候我也有段很愛玩電腦遊戲的時光,但玩法跟常人略有不同。眼拙手殘外加沒耐性,不管動作遊戲還是冒險遊戲,對我來說最大的樂趣不在苦練破關,而是偷改遊戲存檔或資料檔,讓角色在遊戲世界有花不完的錢、穿被打如蚊叮的裝,外加天生神力用小木棍也能捅死大魔王,說穿了其實就是作弊啦~ 年輕同學們可能會覺得,作弊有什麼好說嘴?上網 Google 改法、找現成工具不就好了,甚至有些遊戲就內建作弊碼... 嘖嘖嘖,那可是網際網路跟 Google 仍是科幻電影概念,查資料只能去書店跟圖書館的年代,想作弊一切要靠自己(後來 FPE 整人專家、GameBuster/GameMaster 等專業作弊工具問市後門檻有下降)。我玩遊戲的 SOP 是:學習玩法熟悉環境->出新手村->存檔->打怪/購物/吃補給->存檔,比較兩次存檔差異變化推測體力(HP)、法力(MP)、金額、裝置屬性/數量在存檔的位置,修改數值重新進入遊戲,角色從此無敵又多金。(職業病是對 0x63、0x3e7、0x270f 等數字特別敏感 XD,延伸閱讀:32767! - 黑暗執行緒 ) 更進一步,有時還需得寫程式跑自訂邏輯解析從資料檔案讀出地圖、迷宮路線、武器道具清單...

現在想想,我今天射茶包如果還算得上「快狠準」,或小工具程式寫來駕輕就熟,有一大部分就來自當年破解遊戲累積的技能點數與經驗值。

前陣子為了小木頭有興趣要擔任當然助教,認真對 Arduino 做了番研究,做完光敏電阻、伺服馬達整合實驗我閃過一個念頭,想起之前見識過小閃光玩的一款手機遊戲 - 別踩白塊兒(Don't Tap The White Tile),只見她雙手在螢幕上輕巧飛舞,手拙如我只能徒呼負負,這輩子應無緣看到她的車尾燈。而學會用程式控制伺服馬達後,我有個點子,何不用 Arduino + 光敏電阻 + 伺服馬達 做一個別踩白塊兒硬體外掛來打敗她呢? 於是開始了我偉大的硬體外掛研發計劃~

光敏電阻精準度不佳比美物理亂數產生器,是我學生時代痴心妄想 DIY 土砲光學掃瞄器時就知道的事,研究發現別踩白塊兒一代的黑塊一開始是純黑色,與純白背景色差夠大,初步測試在光敏電阻可識別範圍內,但進階模式或二代的黑塊會變淡誤背景變花,誤差就會大到難以接受,嚴格來說我需要更可靠的偵測元件,但志在好玩,試試也好。

至於模擬觸控動作,除了用伺服馬達抓觸控筆,先前就知道平板手機的多點觸控原理是偵測電容改變,我也試了金屬片 + 繼電器控制接地的玩法:

影片

初步測試可行(如影片所示),但有兩個問題:第一,繼電器切換的機械動作頗外,且壽命次數有限,連續高速切換一陣子聲音就怪怪的,感覺經不起這番狂暴猛操。第二點更麻煩,實測接地觸發電容變化最多只能同時模擬兩點,且偶爾會莫名失敗,推測與原理有關,但我欠缺電子學背景難以再深入探索。支線任務只能到此結束。

回歸「伺服馬達抓觸控筆點螢幕」的老路數,網購找到一枝 2 元的超便宜觸控筆當實驗材料,挖出小木頭/小閃光以前科學夏令營的實驗器材積木,再去光華補了四顆大尺寸光敏電阻(猜想面積大精準度會高一點),就可以組裝出硬體外掛模組囉。

外掛模組從底部看長這樣,一個伺服馬達加一枝觸控筆加一顆光敏電阻為一組,程式以特定週期讀取光敏電阻數值,當讀數小於一定值代表該處螢幕為黑色(亮度愈低數字愈小,且每顆光敏電阻數字不一,最好要能個別設定),就控制伺服馬達轉動將觸控筆下壓模擬出觸控動作。

至於程式開發環境,在試過用 Visual Studio 寫 Arduino 程式後我就回不去了。少了 IntelliSense、F12 Go Definition、Find All Refenrences、變數更名... 有種步槍被沒收只給大刀要上戰的哀戚感,吃過牛排很難不嫌棄陽春麵啊,呵 Orz

組裝完成來實測一下,薑! 薑! 薑! 薑~ (註:為免沈悶,影片速度有調快25%)

影片

就醬,繼三十年前在單機遊戲寫程式作弊後,三十年後我解除「打造實體外掛玩遊戲 App」的成就。

回到初衷,想打敗小閃光的金手指,這玩意兒還有超大的改進空間,當進階模式黑塊變淺或背景不白,光敏電阻讀數便會上升導致失誤率大增,而現有下壓觸控筆的做法速度過快常會失效,可能得從材質與方式下手,但再繼續深入就沒這麼輕鬆好玩了,先到此為止吧,呵~

題外話,如果只求破解別踩白塊兒,根本不用這麼麻煩,用張衛生紙就成了,哈!

【茶包射手日記】ASP.NET 2.0 Web Site 升 3.5 怪異問題一則

$
0
0

分享在同事專案發現的有趣茶包。

同事接手超古老的 ASP.NET 2.0 WebSite 專案,第一步先升級成 3.5,從青銅器時代推演到鐵器時代。依過去經驗,3.5 也基於 2.0,除了要將 AJAX 擴充功能轉成內建,幾乎是無痛升級。(事實上,2.0 就算升 4.0/4.5 也很少遇到麻煩)

但這回遇上怪事。未升 3.5 前 Build Web Site 成功,升級後出現缺少 ASP.NET AJAX Toolkit版本問題, 排除過程冒出一個編譯錯誤,某個共用類別存取了外部程式庫元件的 internal屬性。程式寫法挺妙的,舉例來說,元件 CommLib.Data.DataHelper 有個內部屬性 connString,Web Site 裡的共用類別為了要存取它,故意把自己的 namespace 也改成 CommLib.Data,假裝跟程式庫成為一家人,彷彿這樣就可以存取到 internal 屬性! 依我所知,internal 限制同一組件內存取,要開放限制需由 internal 一方主動宣告(參考:【笨問題】紅杏出牆的internal類別),將 namespace 取一樣就想變自己人,跟改姓就能繼承遺產一樣天真。

但事實擺在眼前,原本 2.0 Build Web Site 是成功的,改成 3.5 才出錯,莫非 2.0 跟 3.5 行為不同? 3.5 底層仍是 2.0,不夠有此差異。取來專案,逐步剔除無關程式反覆測試,當搞清楚是怎麼一回事,我差點笑了出來~

首先,跨組件存取 internal 成員這事兒從頭到尾都是不可行的,不管在 2.0 還是 3.5 都是編譯錯誤。那 Build Web Site 可以成功又是怎麼一回事?後來我才注意到,這顆亂來的共用類別被誤放在網站專案的根目錄下,依據 Web Site 規則,不屬於任何 ASPX、ASCX 的共用程式應放在 App_Code 才能被編譯及引用,換言之,這個問題共用類別根本是一段沒用到的有錯廢 Code。推測還有一種可能,該 internal 屬性更早前曾被宣告為 public,但在 Web Site 根目錄放共用程式純屬白忙一場(幹這種傻事還會被 Visual Studio 警告,如下圖),編譯不到也用不了,至於為什麼專案會留著這段廢 Code 則是謎。

雖然 Build Web Site 會忽略放在根目錄下的共用類別,但如果在 Visual Studio 開啟它,Error List 倒是會忠實指出錯誤。所以,先前的怪現象是這麼來的 - .NET 2.0 時 Build Web Site 成功時沒開啟問題類別,升 3.5 後於修正過程在 IDE 開啟此一存取 internal 屬性類別,Error List 報錯,而我們誤判這是升 3.5 導致的問題(其實問題一直都在,只是先用沒用 VS2017 開啟它就眼不見為淨)。搞清楚這點,先前無法解釋「升 3.5 出錯後再降回 2.0 錯誤也不會消失」現象也有了合理解釋 - 有沒有錯誤要看你有沒有在 VS 開啟它,跟 2.0/3.5 無關。

用一段展示重演狀況,我故意在 Web Site 根目錄下放了一個 BadClass.cs,裡面有一段無厘頭程式碼 BadClass Bad Bad Bad;,確保此類別絕對無法被編譯。而如操作所示,一開始 Build Web Site 成功,Error List 無錯誤,開啟 BadClass.cs 後,Error List 出現三項錯誤,Solution Explorer 跟 BadClass.cs 冒出紅蚯蚓,但 Build Web Site 依舊是成功的!

真相大白,學到 Web Site 編譯行為的冷知識,收工~

網頁偵錯鬼問題 - 開了 F12 開發者工具就正常?

$
0
0

分享今天卡到陰,耗掉我半小時青春的鬼問題。

有個待辦事項清單網頁,使用者可點選待辦項目以 Modal Dialog 連上位於其他主機的網頁執行作業,待 Modal Dialog 關閉,待辦清單需依據執行結果決定是否將該筆作業註記為已完成,避免重複處理。由於待辦清單與執行作業網站分處不同伺服器,跨站台情境無法使用 returnValue 傳回結果,故我會靠另設狀態程式從中傳話以克服限制。(細節可參考 TIPS-跨Domain傳遞Modal Dialog結果

情境示意如下:

//開啟另一台主機的網頁處理資料
var url = "httq://serverB/edit?id=" + item.Id;
//在url後方加上callback以更新狀態
var fixedUrl = url + "&xss_cb=" + encodeURIComponent("http://serverA/update_state?id=" + item.Id);
//使用Modal Dialog模式開啟serverB上的網頁,若作業成功呼叫xss_cbo傳入的serverA網址更新狀態
window.showModalDialog(fixedUrl, window);
//Modal Dialog關閉後檢查狀態是否更新已完成
$.get("check_state?id=" + item.Id ).done((res) => {
	if (res == "OK")  {
		//將該筆資料註記為已完成
	}
});

同事報案,這段狀態更新機制怪怪的,有時執行動作卻沒註記完成,有時則明明沒執行,只是關閉視窗項目就被標為完成,歸納不出什麼規則。

將測試拉回我的主機進行分析,在 JavaScript 加入 alert() / console.log() 試了一陣子,結果依然好時壞,動用 F12 開發者工具跟 Fidller,依然沒頭緒。

不久後,我發現一個現象,每當開啟 IE F12 開發者工具設了中斷點偵錯,程式就運作正常,關閉 F12 Bug 就開始作怪,再開啟 F12 想一探究竟又消失,這 Bug 彷彿有靈性似的... Orz

鬼打牆近十分鐘,從 Fiddler 再找到一條線索 - 關閉 F12 出問題時,Fiddler 不會出現 $.get("check_state?id=…") 封包! 但由 console.log() 軌跡則顯示 $.get() 有被執行。

啊! 是 Cache!!!

我也瞬間明白,為什麼一開 F12 時網頁就正常。答案就在下圖中,我的 $.get() 網址忘了加入亂數,因網址相同有可能會讀到先前 Cache 的內容;開啟 F12 時,因啟用了「一律從伺服器重新整理」,排除被 Cache 誤導的可能,網頁就正常了。

問題在將 "check_state?id=" + item.Id 改成 "check_state?id=" + item.Id + "&_=" + Math.random() 後修復,而我,花 30 分鐘上了一課,遇到「一開啟 F12 問題就消失」的情境,請優先檢查是否與 Cache 行為有關。(註:Chrome F12 也有 Disable Cache 選項,類似現象也可能上演)

解決 TFS 本機工作區項目過多問題

$
0
0

在公司混得愈久,沾染的專案就愈多(講得好像專案是髒東西一樣,咦,不是哦?),每回改程式查程式的第一步就是從 TFS Get Latest 取回最新版本,日積月累留下後遺症。不知從何時起,Visual Studio 中只要是與 TFS 有關的操作都如老牛拖車,讓我有想爆粗口的衝動(大家都知道,我性急如王藍田呀),最近發現,輸出視窗還冒出以下警告:

TF401190: The local workspace MyComputerName;Jeffrey has 177924 items in it, which exceeds the recommended limit of 100000 items. To improve performance, either reduce the number of items in the workspace, or convert the workspace to a server workspace.

Visual Studio 好心提醒,本機工作區(Local Workspace)裡的項目不宜超過 10 萬筆,否則將衝擊效能,而我的工作區已經累積了近 18 萬個項目啊啊啊啊~

爬文找到的兩個解決方向:1.改用伺服器工作區(Server Workspace)。2.將單一肥大的本機 Workspace 拆成多個。參考文章:

本機工作區允許離線簽出、新增刪除、更名,追蹤更動項目再一次簽入,不用連上 TFS 也能工作,應用起來較輕巧靈活,效率也好。也因如此,本機工作區必須有一套機制監控追蹤 TFS 列管的項目是否被修改過,當追蹤目標數量過於龐大,效能就會變差,這就是本機工作區項目不宜超過 10 萬筆的理由。

我無法接受改用綁手綁腳的伺服器工作區(雖然其上限可達一千萬筆),拆成多個工作區則會增加管理及使用複雜度。仔細想想,我真的需要在本機工作區放入那麼多項目?其實不然。工作區中不少專案項目屬臨時性支援性質,或是路過被抓公差才抓回來的,查完問題修好 Bug 加入功能上線後,幾年內甚至這輩子都不會再去碰它,但從 TFS Get Latest 取回檔案開始,只要狀態由 Not downloaded 變成 Latest 項目就被列管,造成項目數量不斷創新高。刪除本機檔案 TFS 只會註記你要刪除等你 Check In,也無法減少數量。降低本機工作區數量的最有效方法,是將資料夾或檔案狀態還原回 Not downloaded,但操作介面沒提供這個功能選項。

在 Stackoverflow 找到密技一則:Delete Local Folder in TFS - Stack Overflow

在 TFS Source Control Explorer使用 Get Specific Version 功能,類別選 Changeset,Changeset 代碼欄則輸入神奇數字 1,下方兩個複選選項打勾,按下 Get 鈕:

之後神奇的事會發生,資料夾或檔案狀態由 Latest 回到 Not downloaded 了! (Get Specific Version 過程如遇本機檔案與版控不同,還是會跳出排除衝突的操作介面)

不過,另外一個問題來了。十幾萬筆工作區檔案,資料夾結構一層一層又一層,我要怎麼知道去哪些地方移除不需要的檔案?

在 Stackoverflow 找到一則討論,發現用 TFS SDK 可以抓出伺服器工作區項目數量,參考官方文件,我寫出一支列舉本機工作區所有檔案路徑的小工具。

專案要參照 Microsoft.TeamFoundation.Client 及 Microsoft.TeamFoundation.VersionControl.Client:

程式範例如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;

namespace ListWorkspaceItems
{
    class Program
    {
        static void Main(string[] args)
        {
            var uri = new Uri("httq://TFS主機/tfs/集合名稱");
            var tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(uri);
            var vcs = tpc.GetService<VersionControlServer>();
            var workspace = vcs.GetWorkspace("X:\\工作區資料夾");
            var items = workspace.GetItems(
                new [] {new ItemSpec("$/", RecursionType.Full)}, 
                DeletedState.Any, 
                ItemType.File,
                false,
                GetItemsOptions.LocalOnly | GetItemsOptions.Unsorted);
            var files = items.First().Items.Select(o => o.LocalItem).OrderBy(o => o);
            System.IO.File.WriteAllText("E:\\TFS-Files.txt", string.Join("\n", files.ToArray()));

        }
    }
}

依據產生的清單,一一找出短期不再使用的項目用前述密技將狀態還原回 Not downloaded。另外我發現一些不慎簽入的 NuGet packages 資料夾,檔案項目數量驚人,是火上加油的大幫兇。

歷經一番清理,TFS 本機工作區項目超過 10 萬筆警示消失,而 Visual Studio 的 TFS 相關操作瞬間輕巧起來,渾身肥油的痴呆胖子變成身手敏捷的結實男,感覺超爽。搬開工作路上的大石頭,又可以全速前進!

2018 萬金石馬拉松

$
0
0

跑過兩回(20132014)萬金石,賽道風景優美全程交管是迷人之處,這幾年更是一路升級銅質銀質等級。不過,雖然大會貼心備有接駁車,凌晨四點前要趕到台北車站搭車,兩點多就得起床,其實挺爆肝的,在我心中並不甜,後來就沒再跑,今年原本也沒計劃要報。萬金石是台灣少數要抽籤的熱門賽事,心想反正抽籤不花錢就隨手填了資料,開獎後驚聞跑步老搭檔忠孝哥沒中籤扼腕不已,感覺自己的中籤信正在閃閃發光,腦波一弱就... (回想起之前排隊買釣鐘燒,原本打算買一盒嚐鮮,快排到時看到「每人限購兩盒」,拎北一怒就兩人買了它四盒 XD)

總之,早上兩點起床,三點半趕到台北車站,嘩~ 排隊人龍綿延五百公尺,從中山北路口排到重慶北路口又折回來... 車子班次很密集,人潮消化挺快,倒是很快搭上車,夜深車少沒多久便抵達會場。

大會依選手 PB 訂了 ABCD 分區起跑,但列印號碼布時出了包全印成 A 區,後來又補寄紙手環補救。報名時不要臉地填上去年在渣打馬吃錯藥跑出的 Sub 4,這在萬金石只能排到 C 區,隨著跑馬風氣盛行,台灣跑者的水準愈來愈高了!

沒刻意提前到起跑點卡位,起跑前十分鐘人山人海,差點連賽道都擠不進去,七千人的賽事好可怕~ (還是鍾愛小而美)

起跑時多雲,天氣還不錯,全程交管賽道筆直,決定認真跑一下,即使破 PB 無望也不好太荒唐,收起手機認真跑。銀質賽事有些專屬特色:電視轉播、菁英選手個人補給區(下圖右):

每 5 公里就有一個感應點,賽後可以看到完整分段成績,我經典的虎頭蛇尾配速無所遁形~

前 30K 還可以,耗時 3 小時 4 分鐘,均速約 6 分速,而這全拜川內優輝所賜! 話說, 14K 左右見對向前導車迎面而來,心理預期後面跟著黑人選手領先群,但這回我卻只看到一名亞洲面孔選手獨跑,疑惑了五秒鐘才在心中吶喊,啊啊啊,那猙獰表情,是川內優輝是川內優輝,而更震憾的是,與川內錯身好一陣子才看到後面兩位黑人選手賣力追趕中,川內狠狠地領先超過一分鐘啊啊啊啊~ 川內彷彿用生命在跑馬的這一幕,深深烙進我心中(我應該一輩子都忘不了),點燃我的跑步小宇宙,努力維持速度不掉... 就這麼支撐著我跑到 30K。

太陽愈來愈大,氣溫持續上升,太陽曬久了頭腦也就清醒了,什麼熱血什麼小宇宙啦,都這把年紀了跟人家拼什麼... 拍完 32K 里程板,對我來說比賽也結束了,北海岸風光如此明媚,吹吹海風散步賞景才不浪費~

30K 後走得多跑得少,進隧道前的最後一段海岸線,在豔陽下格外地美,就欣賞個夠吧!

最後 4 小時近 50 分跑完,參加三屆萬金石最熱的一次,我留下最爛的成績。

參賽人數眾多,終點的幾張桌子獎牌堆成山,形成有趣的畫面。

 

就醬,Y 拖第二馬完成。愈來愈習慣穿著 Y 拖練跑跟比賽,展開始不一樣的馬拉松七年級生涯。


CODE–URL調整HTTP/HTTPS、Port、QueryString參數公用函式

$
0
0

前陣子談過用 HttpUtility.ParseQueryString 解析、修改及還原參數的簡便做法,一不做二不休,再來聊聊如果拿到一個 URL, HTTP 要改 HTTPS、主機名稱要換、Port、路徑要改,是不是也有不走字串比對置換的優雅做法?

爬文查到好用的 .NET 內建物件 - UriBuilder(.NET 2.0 時代就有了,我到現在才學會),Scheme(http、https)、Port、Host、Path 均可任意抽換調整,再產生新的 URL,比起自己寫 Regex 比對置省事可靠不少。UriBuilder 已滿足大部分所需,只少了將 QueryString 轉成 NameValueColletion 方便抽換修改參數,查了文件 UriBuilder 並未宣告 seal 禁止繼承,因此繼承 UriBuilder 再擴充參數處理功能是不錯的選擇。

我寫了一顆 FlexUrlEditor 繼承 UriBuilder,建構式傳入既有 URL,融和上回將 QueryString 轉為 NameValueCollection 增刪修改再轉回字串的技巧,參數可透過 FlexUrlEditor["參數名"] 存取,為 UriBuilder 加上編修 QueryString 參數功能。此外,UriBuilder 產出的 URL 字串有個小缺點 - 它包含 :80、:443 等習慣上會省略的 Port,所以我在輸出時加了點工,使結果更符合平日慣用格式。程式碼如下:

using System;
using System.Collections.Specialized;
using System.Text;
using System.Web;

public class FlexUrlEditor : UriBuilder
{
    private readonly NameValueCollection collection;

    public FlexUrlEditor(string url) : base(url)
    {
        collection = 
            HttpUtility.ParseQueryString(new Uri(url).Query, Encoding.UTF8);
    }

    public string this[string key]
    {
        get { return collection[key]; }
        set
        {
            if (value == null && collection[key] != null)
            {
                collection.Remove(key);
            }
            else
            {
                collection[key] = value;
            }
        }
    }

    public string GenUrl()
    {
        //REF: https://goo.gl/gHmGUz
        base.Query = Uri.EscapeUriString(
            HttpUtility.UrlDecode(collection.ToString()));
        //remove 80/443 port for http/https
        if (Scheme == "http" && Port == 80 ||
            Scheme == "https" && Port == 443)
        {
            //https://stackoverflow.com/a/2819529/288936
            return base.Uri.GetComponents(
                UriComponents.AbsoluteUri & ~UriComponents.Port,
                UriFormat.UriEscaped);
        }
        return base.ToString();
    }
}

寫幾行程式實測,試了修改、刪除與增加 QueryString 參數,https 改 http,Port 變更等(Host 與 Path 也都可置換忘了示範),均得到預期的結果。

using System;

namespace ConsoleApp1
{
    class Program
    {

        static void Main(string[] args)
        {
            //Add parameter
            var url = "http://blog.darkthread.net/";
            var builder = new FlexUrlEditor(url);
            builder["a"] = "1";
            Console.WriteLine(builder.GenUrl());

            //modify and remove parameter
            url = "http://blog.darkthread.net/?a=1&b=2&c=3";
            builder = new FlexUrlEditor(url);
            builder["b"] = null; //set null to remove
            builder["c"] = "中文"; //will be encoded
            Console.WriteLine(builder.GenUrl());

            //single command (C# 6.0+)
            var newUrl = new FlexUrlEditor(
                "https://blog.darkthread.net/?a=1&b=2&c=3")
            {
                ["b"] = null,
                ["c"] = "中文"
            };
            Console.WriteLine(newUrl.GenUrl());

            //read encoded query parameter
            builder = new FlexUrlEditor(
                "http://blog.darkthread.net/?a=1&b=%E4%B8%AD%E6%96%87");
            Console.WriteLine($"a={builder["a"]}, b={builder["b"]}");

            //change scheme & port
            builder = new FlexUrlEditor("https://blog.darkthread.net");
            builder.Scheme = "http";
            builder.Port = 8888;
            Console.WriteLine(builder.GenUrl());

            Console.ReadLine();
        }
    }
}

執行結果如下:

工具箱再添順手工具一支~

【茶包射手日記】詭異的 TypeScript lib.es6.d.ts JSON 重複宣告錯誤

$
0
0

先前處理過幾次 VS2017 TypeScript 版本相容問題,特徵都是專案可編譯但 Error List 有錯誤。在某專案遇到類似狀況,錯誤訊息為 Cannot reclare block-scoped varialbe 'JSON'. @ lib.es6.d.ts。心中警鈴大作,心想應該又是煩人的 TypeScript 版本相容問題。

將 TypeScript 版本從 2.5 更新到 2.7,VS2017 也更新到最新版,問題卻不見改善,這才覺得問題不單純。

經過調查,問題根源讓人啞然失笑,原來專案裡有時代的眼淚 - JSON2.js,Visual Studio 會將專案內含的 JavaScript 也納入 TypeScript 參照範圍,JSON2.js 裡宣告的 JSON 全域變數與瀏覽器內建物件定義檔(lib.es6.d.ts)衝突。

專案網站的主要瀏覽器是 IE,IE8 起已內建 JSON,原本擔心啟用 IE7 相容模式時未內建 JSON 物件才加掛 JSON2.js,經實測 IE11 即使開啟 IE7 相容也有 JSON 可用,而實務環境殘存的 IE678 餘孽必須死,故放心將 JSON2.js 移除,結案!

【茶包射手日記】型別更名後 Visual Studio 編譯仍傳回找不到原名稱錯誤

$
0
0

文章標題很繞口,情境也有些複雜,先來個戰情簡報:

我有個共用程式庫專案 MyModels.csproj 同時被加入 A.sln 跟 B.sln 兩個解決方案,A.sln 開發過程發現 MyModels 某類別名稱有錯別字,Substitute 誤寫為 Substitue (結尾少一個 t),幸好專案仍在開發階段,趁早更正,省得日後每次見到心煩。使用 Visual Studio 的更名功能一下就搞定,A.sln 重新編譯跟測試都正常。

回頭修改 B.sln,解決方案包含了 MyModels.csproj,裡面的 Substiute 類別名稱已更新,但編譯時引用該類別的 ASP.NET MVC Controller 卻冒出以下錯誤:

Error    CS7069    Reference to type 'Substitue' claims it is defined in 'MyModels', but it could not be found   

詭異的是,透過 Visual Studio F12 Go to Definition 查到的都是更正後的名稱 Substitute,Intellisense 帶出的也是 Substitute,按下 F5 Start Debugging 或 F6 Build Solution 時 Visual Studio 卻抱怨 MyModels 裡沒有名為"Substitue"(之前拼錯的舊名稱)的型別。

推測可能跟 Visual Studio 的組件快取仍是舊版有關,但過去的經驗多與 NuGet packages 有關,發生在由 sln 直接參照的 csproj 還是第一次。爬文找到一篇文章:Visual Studio Cache Cleanup

照著文章介紹的做法,先關閉 VS,找到 %USERPROFILE%\AppData\Local\Microsoft\VisualStudio\VS版號\ComponentModelCache 資料夾,將其中的四個檔案刪除,再重新啟動 VS,編譯錯誤果然消失~

問題起因於 Cache ,移除並重新加入專案參照應該也能解決問題。如果以上做法都不管用,該文章還有進一步重設 VS 設定的做法,必要時可參考。

WCF 比 ASP.NET Core WebAPI 更快?

$
0
0

同事轉了一篇探討 WCF 與 ASP.NET Core WebAPI 效能的比較文章: Is WCF faster than ASP.NET Core- Of course not! Or is it?結論出乎意料。

作者看到一則 Reddit 上關於 WCF/ASP.NET WebAPI 效能討論 (Reddit可想成國外的 PTT) 就認真了,跑了蠻專業的測試,試了多種組合,WCF 幾乎都輕鬆將 ASP.NET Core WebAPI 甩在身後。進一步分析,JSON Serializer 似乎是速度的關鍵,當物件複雜化,改用 MessagePack 格式序列化的 ASP.NET/ASP.NET Core WebAPI 組終於以小幅差距打敗 WCF。
 
這個結果顛覆我的想像,我原以為 ASP.NET Core 跟 OWIN 一樣採模組化設計,處理環節可自由切換組裝, 相較內建支援各種 Binding 管道邏輯的 WCF,ASP.NET Core 的 Middleware 概念,沒用到的就不載入,理應更輕巧,Overhead 更低才是,沒想到竟然大輸。而由更換 Serializer 變快這點,推測慢的關鍵可能出在以彈性易擴充著稱的 Json.NET 上,但測試裡採用 DataContractJsonSerializer 的 SmallWcfWebJson 依然大勝 SmallAspNetCoreUtf8Json,則狠狠打臉 Json.NET 是頭號戰犯的推論。 (WCF 採用的是比 Json.NET 還慢的 DataContractJsonSerializer [依據 Json.NET 自家評測],而
Utf8Json 又比 Json.NET 更快,SmallWcfWebJson 比 SmallAspNetCoreUtf8Json 快這點足為 Json.NET 的不在場證明)。

最後,我的個人看法是 ASP.NET Core 雖主打 Middleware 可抽換組裝,但作者測試時採用的是 WCF 及 ASP.NET Core 預設值,故測試數據是二者預設的效能表現,而非其極限。依 ASP.NET Core 的設計哲學,允許你拆除座椅、丟棄備胎、拿掉安全氣囊,不計代價減重以提升賽車速度,其可調整空間遠遠勝過 WCF,但在該測試中並未發揮展現。

不用說,文章在社群引發論戰,一堆人跑出來抗議競賽不公,所以作者補了第二篇文章:Revisited- Is WCF faster than ASP.NET Core- Of course not! Or is it?重申一些觀點並補充幾項測試:

  1. 速度從來不是選擇 WCF 或 WebAPI 的唯一考量
  2. 測試觀測對象是 Latency(延遲),並不等於效能
    作者推測 ASP.NET Core 與 Kestrel 被設計成可以處理大量同時湧入的 Request,單一 Request 延遲較高可能是換取網站整體效能提升的代價。這有點像是「提高 CPU 時脈」、「加大 M1/M2 Cache」與「增加 CPU 核數」間的抉擇,各有其擅長的情境。
  3. 依網友回響,作者改用 Overhead 較低的 HttpWebRequest 重寫 WebAPI 測試,有效提升效能,這回 MessagePack 及 Utf8Json 版 WebAPI 打敗 WCF。
  4. 作者加碼再測了 NetTcpBinding,WCF 提升一倍速度,打敗 MessagePack 版 ASP.NET Core WebAPI。
  5. MsgPack.Cli 表現遠不如 MessagePack
  6. ZeroFormatter 小輸 MessagePack

看完兩篇文章,歸納心得如下:

  • 在預設配置下,WCF 的 HTTP 延遲比 ASP.NET Core Web API 低,抽換改用 MessagePack 或 Utf8Json 序列化才勝出
  • HTTP 延遲不等於效能,有可能單一 Request 延遲較久,但整體網站效能反而更高。
  • ASP.NET Core 可依需求調整優化 Middleware 層,光憑這點,我深信 ASP.NET Core 比 WCF 更容易調出超變態的效能數字。但跟拆掉座椅改用超薄鈑金追求賽車極速數字一樣,為求速度不顧安全、穩定、可靠性,在實務上意義不大。
  • 測試數據差異只在毫秒等級,在實務環境混入資料庫存取、檔案讀寫、複雜商業邏輯運算後,這點差異往往微不足道,除非其他環節都已優化到毫秒等級而且 Request 數量驚人,否則很難有感。
  • 會因為這個評比改用 WCF 嗎?
    不會。API 平台選擇仍維持原判:閒談:.NET Remoting、WCF、WebAPI、Socket,該怎麼選?
  • 會因為這個評比改用 Utf8Json 或其他更快的 JSON 序列化程式庫嗎?
    除非 JSON 序列化是效能瓶頸,否則不會。.JsonNET 我已經用得很順手,先前遇過不少刁鑽需求,其功能完整性與擴充彈性都沒讓我失望過,「速度不是最快的」不足成為換掉它的理由。

最後補上一段小插曲,這個議題引來同事與我的有趣對話:

同事:這讓我有點懷念起WCF... 看起來 WCF 沒那麼不堪, 對吧?
黑大:No Way,我絕不會為這點小確幸回頭。 WCF 的不堪從來不在效能上,而是設定不對或運作不正常時讓你查到痛不欲生啊~ 就像很帥但動不動會打人的前男友,或是超正但三不五時爆發公主病的前女友。
同事:我對 WCF 從來沒深入過,也許是我自己功力不夠,哈。
(稍後)
同事:想想也對,自殘型男友是這樣沒錯:是我不夠了解女友,都是我的錯... blah blah

哈!

ORACLE筆記-使用 CONNECT BY 呈現階層化資料

$
0
0

很久很久以前(轉眼已十年惹)學會用 SQL 2005 Common Table Expression呈現資料表中的階層式資料(組織圖、BOM表),當時還學到 ORACLE 有個 CONNECT BY 語法效果類似。說來慚愧,對它只停在「知道」的層次,沒實地玩過,直到今天遇到必須使用 ORACLE 查詢展開組織圖的需求卻寫不出來,確認了「其實我不會」。

亡羊補牢,猶未晚矣,特筆記今天的實地演練心得備忘。

測試資料直接借用 CTE 文章裡的 BOM 範例:

使用以下指令產生測試資料:

CREATE TABLE BOMDemo (
       PartNo VARCHAR2(8),
       PartName NVARCHAR2(16),
       ParentPartNo VARCHAR2(8)
);
INSERT INTO BOMDemo VALUES('1','PC','ROOT');
INSERT INTO BOMDemo VALUES('2',N'主板模組','1');
INSERT INTO BOMDemo VALUES('3','CPU','2');
INSERT INTO BOMDemo VALUES('4','RAM','2');
INSERT INTO BOMDemo VALUES('5',N'主機板','2');
INSERT INTO BOMDemo VALUES('6',N'CPU散熱器','2');
INSERT INTO BOMDemo VALUES('7',N'滾珠風扇','6');
INSERT INTO BOMDemo VALUES('8',N'散熱鰭片','6');
INSERT INTO BOMDemo VALUES('9','StorageCage','1');
INSERT INTO BOMDemo VALUES('10',N'DVD燒錄器','8');
INSERT INTO BOMDemo VALUES('11','HD','8');
INSERT INTO BOMDemo VALUES('12','FDD','8');
INSERT INTO BOMDemo VALUES('13',N'機殼模組','1');
INSERT INTO BOMDemo VALUES('14',N'電源供應器','13');
INSERT INTO BOMDemo VALUES('15',N'機殼框架','13');
INSERT INTO BOMDemo VALUES('16',N'面板','13');
INSERT INTO BOMDemo VALUES('17',N'側板','13');

ORACLE 官方文章 Hierarchical Queries有 CONNECT BY 使用說明,寫得頗為詳細,vr 還有範例可參考。

整理重點如下:

  • START WITH 條件被用來指定樹狀結構的起始點
  • CONNECT BY 條件用來定義從屬關係,由於關聯欄位分別來自父資料列與子資料列,父資料列欄位名稱需加上 PRIOR 運算子以為區別
  • LEVEL 可傳回該筆資料所在階層
  • 產生縮排可用 LPAD(' ', 4 * (LEVEL – 1))
  • 神奇的 SYS_CONNECT_BY_PATH(ColName, '/') 能自動組裝出 "Parent/Child/GrandChild",超方便
SELECT PartNo,
--內縮排版可用LPAD()實現
LPAD(' ', (Level - 1) * 4) || PartName AS PartName,
--SYS_CONNECT_BY_PATH()可快速串接各層欄位字串
SYS_CONNECT_BY_PATH(PartName,'/') AS PartPath,
Level
FROM BOMDemo
--以ParentPartNo='ROOT'這筆做為起始點開始長樹
START WITH ParentPartNo='ROOT'
--欄位名前方的一元運算符PRIOR用於指定父資料欄位
--故此處為"將PartNo等於本筆ParentPartNo的資料列視為父資料列"
CONNECT BY PRIOR PartNo = ParentPartNo

實測結果如下:

與 SQL CTE 遞迴呼叫相比,CONNECT BY 寫法簡潔不少,ORACLE 順手兵器 +1。

2018 三重馬

$
0
0

三重馬第三回(2015 2017)。

三重馬在我心中是累積全馬業績的優質賽事,報名費親民、交通便捷、賽道平坦、路線單純、四月氣候冷熱不致極端,補給單調但不愁短缺,賽事小而美,加上主辦單位經驗豐富,幾無踩雷風險,是穩穩賺進一馬的好選擇。

這半年來我調整了練跑方式。從美津濃、迪卡儂進化到 Y 拖(愈來愈省錢惹,呵),不再繞跑道改跑河演,晨跑距離也拉長到 10K 為一個基數,久而久之,速度雖未明顯變快(原因是不敢練間歇,老爺車經不起摧殘啊),但長距離跑來倒是日益輕鬆,已能不太費力 SUB2 跑完 21K,也算一種進步啦! 不過這多少也拜冬末春初涼爽之賜,待入夏天氣漸熱,說不定又是完全不同的光景,呵。

五點多抵達重陽橋下,改穿 Y 拖後不再擔心鞋濕黑指甲,低溫微雨對我而言是僅次於乾冷的絕佳跑馬天氣。

六點準時開跑,這幾年馬拉松賽事多如雨後春筍,稀釋了每場參加人數,小而美賽事的人又更少了(事後看完賽證明只有不到 500 人),希望我鍾愛的小而美賽事不會因此無以為繼。

跑三重馬就是單純的跑一場全馬,就專心的配速 1K 1K 推進,途中不多逗留。關渡穚這段賽道光去年就跑了三四回,但如同狗見電線桿就抬腳,看到關渡橋就是想拍照,反射動作擋都擋不了。(中途一度雨勢轉大,鏡頭上的水珠可見一斑)

跑完 10.5 K 來到北方折返點,媽媽嘴咖啡。

折返再跑 10.5K 回到起點,全馬要再跑第二趟。

九點後雨勢轉小,河濱道開始湧現一批批自行車大隊,得時間提高警覺,但還不致影響行進就是了。

天公作美,加上近況不錯,跑得挺順,前後半馬時間幾乎相同,後半馬沒有掉速,是虎頭蛇尾大叔的跑馬生涯的新里程碑,呵!

不料,這回換我的 Fenix 3虎頭蛇尾,前 30K GPS 錶里程對照大會單趟 10.5K 幾乎完全一致,誤差不到 200 公尺,最後 10K 卻整個大爆走,配速顯示忽快忽慢(連2分速都出現惹),應是 GPS 軌跡嚴重飄移的結果。事後看圖,果然真飄到對岸兒童新樂園去了,終點衝線時還在右岸回不來... Orz

有趣的是,下圖是大前年 Bryton C40 曾上演的 GPS 大迷航,差不多發生在同一區域,莫非這裡隱藏著神祕力量或是有個關渡三角洲? XD 

GPS 失準後全憑感覺配速,不知剩餘距離油門踩重了,跑出比預期好的成績,完賽晶片時間怡巧落在 4:12:00.00(精準到微秒,哈),均速六分速,再下一馬。

本屆獎牌挺特別,大會說是拱門造型,但弧形與寬高比例的巧合,配上灰色,讓不少跑友聯想到清明節應景的「百年之後專屬門牌」,幸好沒把跑友姓名刻上去,哈! 排除這項毛毛的巧合,其實還不錯看。

HTML5 筆記 - 使用 SVG 取代 PNG/GIF 圖示

$
0
0

隨著主流瀏覽器支援度愈來愈好,在網頁裡用 SVG 取代 PNG、GIF 呈現圖示(Icon)及簡單圖案漸成趨勢,沒吃過豬肉也要看看豬走路,趕緊做點功課並整理筆記備忘。

網頁引用 SVG 有三種做法:

  • Object: <embed src="…"> <object data="…"> <iframe src="…">
  • Inline: <svg>…</svg>
  • Image: <img src="…"> background: url('…')

SVG 於 2001 年問市,標榜向量圖型縮放無鋸齒、靠宣告就能做動畫,還可與 JavaScript 互動。早期瀏覽器(IE6 開始)支援 SVG 需依賴外掛套件,必須以 Object 方式(<embed> <object> <iframe>)嵌入 SVG 檔,由外掛套件負責呈現及互動操作,JavaScript 能做的互動很有限。

現代瀏覽器均已內建 SVG 支援,在 HTML 可直接加入 <svg> 成為網頁一部分,使用上與一般網頁元素沒什麼兩樣,可經由 JavaScript 存取修改繪製內容,可透過 CSS 控制顏色、線條粗細(例如: svg path { stroke: black; stroke-width: 1px; fill: none; }),變化無窮。(註: 如要使用較新的 CSS 技巧,需留意 IE 及 Edge 是否支援)

來段簡單示範。我在以下網頁我用 JavaScript / jQuery 動態產生 SVG 畫出三角形,並在按鈕事件修改 <svg width> 放大到 100px 寬,另外用 CSS :hover 樣式製造出線條加粗並灰階化的滑鼠滑過特效。Live Demo

<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>SVG Demo 1</title> </head><body><style>
    svg:hover path {
      stroke: #444;
      stroke-width: 2px;
      fill: #ccc;
    }</style><script src="https://code.jquery.com/jquery-3.1.0.js"></script><button id="sc">Scale Up</button><div style="padding: 3px;"><svg id="s"></svg></div><script>
    var s = $("#s");
    //使用JavaScript產生SVG
    s.attr("viewBox", "0 0 20 20");
    s.attr("width", 20);
    s.html('<path stroke="red" fill="yellow"></path>');
    s.find("path").attr("d", "M2 2 L18 10 L2 18 Z");
    $("#sc").click(function() { s.attr("width", 100); });</script></body></html>


效果展示:

 

除了以 <svg> 形式嵌入 HTML,另一種流行用法是用 SVG 取代 PNG/GIF 做為 <img> 或 CSS background/content 的圖案來源,適用所有原本引用圖檔的場合(例如元素的背景圖),卻能保有放大縮小不失真的向量特性。其用法跟 .png/.gif 相同,只是將 <img src=""> 或 CSS: background: url(…) 連結換成 .svg 即可。還是一種做法是使用 Data URI,將 SVG 內容直接嵌入 HTML 或 CSS,如此可省去瀏覽器另發 Request 下載 svg 的效能損耗,加快網頁顯示速度。示範如下:

SVG Data URI 除了以 data:image/svg+xml;utf8, <svg>…. 直接串接 XML 內容,亦可將 <svg>…</svg> 內容轉為 Base64 編碼,但如此反而會增加資料量(3 Bytes 變成 4 Bytes,外加 0-3 個填充字元)並失去可讀性及可編輯性。除非要避免 HTML 或 CSS 摻雜 <svg> 干擾閱讀,不然 SVG Data URI 直接放入 XML 內容較有利。

但要注意,IE 不支援以 Data URI 內嵌 SVG,只能連結外部 .svg。如以下測試,兩個 SVG 圖檔分別使用 Data URI 及獨立 .svg 檔方式載入,Chrome / Edge 兩種做法都行(測了 Firefox/Safari 也都 OK),IE11 只支援外部 .svg 作為 <img> 來源,唉...

簡單幾何圖案算算座標或敲指令就可產生,若要求精緻美觀,則是視覺設計人員及專業設計軟體的領域,程序猿們最好閃開讓專業的來。開發時如需要現成圖示圖案,網路可找到不少免費向量圖示,多半有提供 SVG 格式下載,前端攻城獅們可多加利用。

參考資料:


【茶包射手日記】用 TransactionScope 包 LINQ 迴圈查詢出錯

$
0
0

使用者報案某網頁功能故障,經抽絲剝繭鎖定爆炸點在一段「TransactionScope 中以 LINQ to SQL 取回物件集合,跑 foreach 開連線查資料庫」邏輯(警語:跑迴圈執行資料庫查詢,迴圈次數如過高將成效能殺手,宜避免),簡化為以下程式片段可重現問題:(註: 程式碼經過簡化,在 foreach 迴圈裡不斷開啟資料庫連線看起來很突兀,但實際案例中 foreach 迴圈是呼叫外部程式庫的共用函式,要追進去才會知道其中開了連線查詢資料庫)

using (var tx = new TransactionScope())
{
	using (var db = GetLinqToSqlDataContext())
	{
		//using (var tx2 = new TransactionScope(TransactionScopeOption.Suppress))
		{
			var datas = from o in db.SomeTable
						where o.Col1 == "A" || o.Col1 == "B"
						select o;
			foreach (var q in datas)
			{
				using (var cn = new SqlConnection(cs))
				{
					cn.Open();
					var cmd = cn.CreateCommand();
					cmd.CommandText = "select getdate() as d";
					var dr = cmd.ExecuteReader();
					dr.Read();
					Response.Write(dr[0]);
				}
			}
		}
	}
}

錯誤訊息為:

[SqlException (0x80131904): There is already an open DataReader associated with this Command which must be closed first.]
   System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) +396
   System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransaction(TransactionRequest transactionRequest, String name, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) +146
   System.Data.SqlClient.SqlDelegatedTransaction.Promote() +134

[TransactionPromotionException: Failure while attempting to promote transaction.]
   System.Data.SqlClient.SqlDelegatedTransaction.Promote() +756746
   System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx) +63
   System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx) +177

[TransactionAbortedException: The transaction has aborted.]
   System.Transactions.TransactionStateAborted.CheckForFinishedTransaction(InternalTransaction tx) +11
   System.Transactions.EnlistableStates.Promote(InternalTransaction tx) +25
   System.Transactions.Transaction.Promote() +61
   System.Transactions.TransactionInterop.ConvertToOletxTransaction(Transaction transaction) +46
   System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts) +193
   System.Data.SqlClient.SqlInternalConnection.GetTransactionCookie(Transaction transaction, Byte[] whereAbouts) +35
   System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx) +450
   System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx) +4889946
   System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction) +4890005
   System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction) +33
   System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject) +1286
   System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) +65
   System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) +117
   System.Data.SqlClient.SqlConnection.Open() +122
   ASP.testdtc_aspx.Page_Load(Object sender, EventArgs e) in X:\WWW\TestDTC.aspx:28

測試發現如將最外層的 TransactionScope 移掉,或將 foreach 用 new TransactionScope(TransactionScopeOption.Suppress) 包起來不參與交易就不會出錯。

初步推測錯誤原因:LINQ to SQL 查詢傳回的 System.Data.Linq.DataQuery<T> 跑 foreach 並不是一次讀完所有資料再跑迴圈,而是開著 DataReader 每跑一圈讀一筆,當處於 TransactionScope 交易範圍內,此時如試圖另外建立資料庫連線,連線將會升級參與分散式交易,過程中將觸發 There is already an open DataReader associated with this Command which must be closed first. 錯誤。依此原理,先 .ToList() 或 .ToArray() 再 foreach 也可解決問題,經實測也獲得驗證。

進一步推論,問題關鍵在 TransactionScope 中 DataReader 開啟狀態不能再開連線,故即使沒用 LINQ to SQL 也可能踩雷,例如以下程式範例,在 TransactionScope 內 while (dr.Read()) { 開DB連線; } 一樣能引爆,故在 Entity Framework 情境下也可能遇到。

using (var tx = new TransactionScope())
{
	using (var cn1 = new SqlConnection(cs))
	{
			cn1.Open();
		var cmd1 = cn1.CreateCommand();
		cmd1.CommandText = "select 1 union select 2";
		var dr1 = cmd1.ExecuteReader();

		while (dr1.Read())
		{
			using (var cn2 = new SqlConnection(cs))
			{
				cn2.Open();
				var cmd = cn2.CreateCommand();
				cmd.CommandText = "select getdate() as d";
				var dr2 = cmd.ExecuteReader();
				dr2.Read();
				Response.Write(dr2[0]);
			}
		}
	}
}

爬文在 Stackoverflow 找到另一種解法,在連線字串加上 MultipleActiveResultSets=true 開啟 MARS 功能,可解除同時間單一 DataReader 限制避開問題,但有些副作用

  • 針對非 MARS 模式最佳化的老程式搭配 MARS 使用時效能稍差
  • 同時執行多個批次作業時,你跟 SQL Server 會難以斷定 USE、SET、BEGIN TRAN、COMMIT、ROLLBACK 等指令的影響範圍

評估後,將 LINQ to SQL 查詢結果 .ToArray() 再 foreach 是影響最小的做法。

心跳帶心率爆衝問題排除心得

$
0
0

入手第一支 GPS 錶起,多年來已很習慣戴著心跳帶跑步,一方面作為「效能調校」的依據(相同配速,心率愈低代表愈有效率體能愈好),更重要的是提醒自己量力而為不要嗨過頭,一路跑去蘇州賣鴨蛋。多年使用下來,偶爾會遇到心率數字失準狀況,最近又累積一則問題處理經驗,順手整理心得分享兼備忘。

心率帶原理與醫院的心電圖相似,藉由心跳帶的導電橡膠片測量心跳所產生的微小電壓變化,訊號放大後即可由波形推算心跳頻率。因此,心跳帶鬆緊帶內側兩片(我的 Garmin HRM-RUN 舊款心跳帶左右各二共有四片)緊貼皮膚的膠片是導電的,以三用電錶實測中央膠片部分電阻只有 100 歐姆(膠片外框部分不導電)。

因此可知導電性會影響測量結果,於是有個常見現象:戴上心跳帶跑步才在暖身階段,心率便飆上即使全力衝刺都很少達到的破表水準,但隔一陣子再降回正常範圍。長期跑下來,跑多快心跳差不多會有多高都心裡有數,足以斷定這種心跳趨勢明顯不合理,究其原因多半是皮膚太乾燥導電不佳造成,而運動一段時間開始流汗後問題會自然消失,數字便會恢復正常。「不夠潮」所造成的誤差線型類似下圖,典型特徵是開始沒多久心率爆衝,大約 3-5 分鐘後降回預期範圍直到結束。

解決不夠潮問題很簡單,運動前在鬆緊帶的導電片塗點水就成了(心跳帶使用手冊也會提醒,但遇上冬天...)。知名的鐵人部落客 DC Rainmaker 還傳授了幾招私房密技:佩戴前先舔一下(這....)或塗導電凝膠。國外有賣心跳帶專用的導電凝膠,台灣較濕熱,較少乾冷到運動不出汗,沒看過有人在賣,可能要找醫療用的,另外也可以 DIY。依我自己經驗,一般沾點自來水就夠用,但有時要稍等一分鐘確保生效,沾點肥皂效果又更好。

正常情況會像下圖,當保持相同配速,心率會在起跑後快速上升到該配速對應心率區間,之後隨時間緩緩上升。

最近遇到一個比較詭異的案例,因原廠心跳帶脫膠膠片斷裂,我買了便宜的白牌台製心跳帶替換,試跑過一兩回沒什麼問題。但之後陸續出現離譜誤差,最扯的一次是,同樣配速有一段心率低到 100 以下,有一陣子衝上 225,跑出這種心率,拎杯早就歸西了吧?

由於會發生在中後期可排除「不夠潮」,而剛換心跳帶就出狀況,優先懷疑跟新買心跳帶的品質或相容性有關,但也不排除電池沒電或心率監測器故障。印象中電池換不到一年,應該沒那麼快耗盡,而取出電池量電壓仍有 3.05V 左右(新品約 3.2V),加上心率亂跳期間觸地時間跟左右平衡數據無異樣,故先排除電力不足因素。為對照起見,特地找來 Bryton GPS 錶的心跳帶,一次綁兩條,遇到數字有異,立刻將心率監測器換到另一條,實測換心跳帶數字照樣偏高,新買心跳帶洗刷冤屈無保請回。問題回到心率監測器上,推論可能心率偵測耗電較多(或搭配新買心跳帶時需要較多電力),現有電池電壓雖還有 3V 但已在不夠力邊緣,但支持其餘功能(觸地時間、左右平衡、ANT+傳輸)則綽綽有餘,如此可解釋其他數據正常唯有心率亂跑。換上全新電池,心率數字就正常了。心跳帶疑難雜症經驗再添一筆~

最後補充兩篇心跳帶專業文(作者是當年推薦我跑櫻花初馬的馬場前輩 Alex):

ASP.NET 網站部署後發生 ryslon 編譯錯誤-2146232576

$
0
0

同事遇到的案例。

VS2017 新增 ASP.NET 網站專案,專案範本預設參照的 Microsoft.Net.Compilers 版本是 1.3.2。(延伸閱讀:神祕的ASP.NET bin-roslyn目錄) 同事為了使用 C# 7.0 功能,升級 Microsoft.Net.Compilers NuGet 套件到 2.7.0 版,該專案先前已部署測試過,心想 ASP.NET Runtime 版本維持 4.5.2 沒變,應該不會有問題。

殊不知,在開發機的 IIS Express 及 IIS 測試 OK,部署到測試台 IIS,冒出以下錯誤:

在 Stackoverflow 查到一則討論解答了困惑:

The culprit is the Microsoft.Net.Compilers package, used to support modern C# syntax/features (version 6.0, 7.0) in your project and in Razor views in particular. Depending on its version, the package requires a particular minimum version of the full .NET framework to be installed on a machine in question.

For instance, the 2.2.0 package requires .NET 4.6+. Even though your project is targeting say .NET 4.5.2, you probably have the latest .NET installed on your development machine, and everything goes just fine. The remote deployment machine only has .NET 4.5.2 installed, and when your ASP.NET application tries to compile resource (e.g. views) at run time, you get error -2146232576.

意思是新版 Microsoft.Net.Compilers Package 支援在 .cshtml Razor 語法及專案引用 C# 6/7 新語法,但 Package 本身依賴特定的 .NET Framework 版本,即使 ASP.NET 的目標平台是 4.5.2,要使用 Microsoft.Net.Compiler 2.2.0,部署端機器必須安裝 .NET 4.6+,否則在編譯 View 將發生 -2146232576 編譯錯誤。

回頭再看 Microsoft.Net.Compilers Package 說明,原來本草綱目早有記載...

最後,為了簡化日後部署程序,同事選擇降版回 1.3.2 解決問題。而先前我常困惑為什麼在 .cshtml 不能用字串插值 $"…{varName}…",這下也有了解答。

2018 石碇馬

$
0
0

總爬升高度超過一千公尺,在初夏舉辦,又硬又陡的石碇馬,要失心瘋到什麼地步才會年年報到? 201415 1617

天曉得,反正今年我又參加了。

去年全馬人數銳減剩五百人,有點擔心大會會經營困難,今年倒是人氣回升,有好幾組慶祝百馬、兩百馬、三百馬的跑團,熱鬧非凡。

有一輛大會接駁車開錯路,故全馬延遲了幾分鐘出發。

五公里左右彭山公廁旁的小廟 2016 拆除重建,迄今尚未完工。

小廟旁的龍柱靜靜等待著,明年此時不知是否已復行視事。(所以不知不覺間已決定明年還要報名?)

適逢桐花季節,一路上有不少桐花可賞。

花季前期落花不多,加上要趕路,就不排成愛心拍照了,隨便堆個四朵意思到就好。

照片中的鞋印背後有故事。一般來說,剛跑過積水留下鞋印無足為奇,但如果濕鞋印綿延數公里不絕就有點意思了吧?苦追數公里,我終於找到鞋印的主人,一位黑色無袖排汗衫黑短褲穿 New Balance 黃底黑跑鞋的網友(鞋底的黃色類似陸王蠶絲土的顏色),我在心中默默推測鞋內早已汗流成河,辛苦了~

這位攝影大哥很故意,選在一段大陡坡的最高點掌鏡,吆喝著要爬坡爬到牙歪的跑友們快點跑起來,哈!

花了兩小時 44 分完成半馬,來到二格山腳綠豆湯鞍部,眼見天上薄雲快擋不住蠢蠢欲動的太陽,今天就不加碼攻上二格山頂,速速折返。

空氣不太好,沿筆架山望去白茫茫一片。

第一次穿 Y 拖跑石碇馬,深深感覺自己功力不足,下坡腳掌要斜著落地,時間一久腳背連腳踝處的肌肉又酸又僵,連著小腿也緊了來,只能步步為營,無法像跑平地一樣輕鬆。見其他穿 Y 拖的前輩下坡也一臉輕鬆健步如飛,我還有很大的進步空間。

以下為回程順手亂拍,路旁農家的雞。(九點半之後雲 Hold 不住了,太陽露臉)

石碇特產苦茶油麵線店家。(在補給站有吃到,好吃)

八卦茶園。

看到這堆模具就知道終點快到了。

最後,5:38:57 完賽,打破了場地 PB。但我的 Fenix 3 又在 30 公里後 GPS 大錯亂,最後甚至收不到 GPS 訊息,導致最後里程大亂,總距離被灌水到 46K,兩週前三重馬在最後十公里也是 GPS 大暴走,而兩次賽事間跑過數回 10-20K 倒都無異,莫非是手錶硬體出了問題,連續記錄過久元件會過熱? 不過這得連跑 30K+ 實測才能射茶包,太折騰了,再觀察吧。

本屆獎牌挺可愛。

 

最後,推一下大會的用心之處:

全程使用環保杯,補給有苦茶油麵線、翠玉蛋等土產,跟往年一樣多采多姿。

吃到手油油嘴油油有面紙可擦,最難能可貴的,鄉親吶,是凡士林是凡士林是凡士林! 我想起多年前跑萬金石跑到該邊股溝快摩擦生火卻一路找不到凡士林的沈痛回憶,還直接擱在椅子上自取,不怕羞於啟齒,真是太懂得跑者的需要了~

第一名前導車過去很久了,忽然又聽到前導車接近,原來是在引導產地直送的冰塊讓跑友消署。之後一路上有冰汽水、冰啤酒可喝,讚!

小型賽事最怕跑到前無古人後無來者又遇上叉路,大會在所有叉路口都標了黃底箭頭路牌,讓人安心不少。

更衣盥洗室有四支超強力蓮蓬頭,配上冷水,沖起來好痛快~

全馬賽後餐點一樣是在學生餐廳吃冷氣吃飯,今年吃學生自助餐,但我比較懷念去年的排骨飯

CSS 左右貼齊樣式在 IE 產生大段空白

$
0
0

講到 CSS 我充其量只算是票友,但專案遇到問題還是得面對... Orz 本次的案例如下:

使用者回報某網頁用 Chrome 看正常,改在 IE 卻有部分段落的句子前方出現一大段空白,檢視 HTML 原始碼,發現編寫者輸入文字內容時,將段落裡的不同句子拆成多行,前方還加上 Tab 與上方對齊以求美觀。一般來說,HTML 文字出現的空白會被壓縮,接連的換行與空白符號只會被當成一個空白處理(要留白需改用 &nbsp; &ensp; &emsp;),但由於套用 text-align: justify 左右貼齊,這些換行與 Tab 便在 IE 上產生非預期的結果。例如以下範例:

<html><head><meta http-equiv="Content-Type" content="text/html; encoding=UTF-8" /><style>
	article { text-align: justify; }</style></head><body><article><section>故事是這樣的,有個內容網頁,其中一個段落由好幾句話組成。
			製作者輸入內容時,在 HTML 原始碼換行輸入第二句話,前方加了 Tab 好與上一行對齊,各家瀏覽器看來都算正常,除了 IE11...
		</section><article></body></html>

經實測,這段 HTML 在 Chrome、Firefox、Edge 看起來都正常,除了 IE11,

由於 IE 出現的多餘空白會隨瀏覽器視窗寬度變長變短,推測這是 IE 在實作左右貼齊時將換行與 Tab 間隔開的文字視為可以拉長間隔的區塊,靠它調節整行寬度,試著移去換行與 Tab 讓前後文字相連問題即消失,可證明此一假設。掃瞄及修正網頁文字工程浩大,於是我嘗試透過樣式設定補救的做法,找到一解 – text-justify。

text-justify 尚未標準化,各家瀏覽器支援與實作狀況不一。例如,依據 MDN text-justify 有 auto、none、inter-word、inter-character、distribute(已廢除不建議使用) 幾種設定值,而 IE 則有以下選項

  • auto
    預設值,瀏覽器自行決定演算法 (結果搞出一大片留白)
  • distribute
    接近 newspaper 設定,但針對東方語系最佳化(例如: 泰文)
  • distribute-all-lines
    與 distribute 相同,但最後一行也會左右對齊,適用表意文字(Ideographic Text)
  • distribute-center-last
    未支援
  • inter-cluster
    將字與字間無空白文字左右貼齊,適用亞洲文字
  • inter-ideograph
    將表意文字左右貼齊,並同時加大或縮小表意文字及字間空白
  • inter-word
    增加字與字之間的空白,是最快讓所有行寬一致的做法,但不包含段落的最後一行
  • kashida
    阿拉伯文專用的貼齊方式
  • newspaper
    同時增加字母與字的間隙,演算法最複雜,適用於拉丁語系字母

文件涉及不少語言學名詞,我有看沒有懂,實測最準:

如以上展示,auto、inter-cluster、inter-word 會產生多餘空白,distribute、inter-ideograph、newspaper 較正常。最後我選擇用 distribute,用其他瀏覽器觀察也沒什麼問題,以此 Workaround 結案。

Viewing all 428 articles
Browse latest View live