Quantcast
Channel: 黑暗執行緒
Viewing all 2311 articles
Browse latest View live

TIPS - 限定 Visual Studio 使用的 C# 語言版本

$
0
0

同事通報:無法編譯簽入的 C# 專案,部分程式碼存在語法錯誤,細查發現是 Visual Studio 2015 Update 3 不認得以下寫法:

publicstring Id 
{
    get => RawId.ToString();
    set => RawId.Parse(value)
}

原以為這是 C# 6 語法,VS2015 理應支援,進一步確認是 C# 7 才有的新功能(Expression Body Property Accessor),難怪 VS2015 不認得。(其實專案安裝 Microsoft.Net.Compilers 套件後可用 VS2015 編譯跟執行,但必須忍受程式碼有一堆紅色毛毛蟲,更多細節可參考小朱的文章:[C#] 在 Visual Studio 2015 使用 C# 7 - 小朱® 的技術隨手寫)

語法糖不吃不會死,自己想吃糖而逼人升級太傲嬌,乖乖配合大家採用通用版本才不會被蓋布袋是好隊友。

爬文學到一招,VS2015 / VS2017 專案屬性的 Build 設定有個 Advanced 鈕,開啟 Advanced Build Settings 後有地方可以指定專案採用的語言版本:

將語言版本調成 6.0,VS2017 會將 C# 7+ 才有的寫法標示為錯誤並顯示相關提示,如此可確保專案使用 C# 語法與較早版本的 Visual Studio 相容,減少開發團隊合作時的困擾。


【茶包射手日記】IE11 localStorage 為 null

$
0
0

測試一陣子的專案接到回報,某網頁在某台電腦用 IE11 檢視有錯,用 Chrome 或其他電腦的 IE 則無問題。

起初懷疑是 IE 相容模式問題,幸好使用者在同一辦公室,得以親自借用電腦開 F12 查案,追出關鍵點在於問題 IE 的 localStorage 不知何故為 null,而網頁裡的 JavaScript 程式假設網頁會在 IE9+ 或 Chrome 執行,未考慮瀏覽器不支援 localStorage 出錯。

依我所知,IE8+ 即支援 localStorage,而爬文得知,IE 遇 file:// 會禁止存取 localStorage,但此種狀況 localStorage === undefined (如下圖所示),跟觀察到 localStorage === null 的狀況又不盡相同。

靈機一動,改朝「有沒有可能 localStorage 被 IE 停用?」方向追查,果然,在「網際網路選項/進階」有個「啟用 DOM 儲存」選項,預設不勾選,在問題 IE 卻是被選取狀態,將其取消後一切恢復正常:

其實,不管 IE、Chrome、還是 Firefox 都有開關可以停用 localStorage/sessionStorage,只是寫網頁多年都沒想過它可能被使用者停用而疏漏了檢查,錯失在第一時間提供偵辦線索,經此事件長了知識,下回會納入考量。

後記:好奇詢問報案人,怎麼會想到要去停用 DOM 儲存。答案完全不出所料,他沒動過設定,電腦是前人移交的,為什麼會這樣沒人知道。一如往例,每個茶包總會存在某些不可考的謎,除錯人生才如此有趣。 XD

VS2015 Update 3 TypeScript 語法標示失效

$
0
0

Visual Studio TypeScript 版本相容問題又來了~ Orz

同事 Visual Studio 2015 Update 3 的 TypeScript 版本仍在 1.8,配合專案要升級到 2.3 (相容 2.4 版需修改程式), 沒留神裝成 TypeScript for Visual Studio 2015最新版( 2.7.2,應點開 Detail 才能下載歷史版本), 之後發現版本不對再安裝了 TypeScript for Visual Studio 2015 2.3.3。

之後就是一連串打怪的過程:

最開始是 Visual Studio 2015 錯誤清單冒出大量 TypeScript 錯誤無法編譯,推測是 TypeScript 引擎被換成最新版。移除 TypeScript for Visual Studio 2015 2.7.2 後可成功編譯。

移除 2.7.2 後專案可以編譯沒錯,但 TypeScript 的 IntelliSense 出了問題,跳出的都是以 JavaScript 角度解析的提示項目。
懷疑 2.7.2 是換掉 Visual Studio 使用的 TypeScript 元件,移除 2.7.2 時元件也被移除導致功能異常。

接著我嘗試移除並重裝 2.3.3,沒想到結果更慘,這下子連 TypeScript 語法標示(Syntax Highlighting)都不見了,IntelliSense 徹底消失,只剩文字檔編輯功能。

知道是版本問題,但要用什麼關鍵字爬文毫無頭緒。多試幾下,在專案屬性視窗 TypeScript Build 找到較明確的訊息:

An error occurred trying to load the page.
No exports were found that match the constraint:
  ContractName  Microsoft.CodeAnalysis.Editor.TypeScript.ScriptContexts.ITypeScriptProjectProvider
  RequiredTypeIdentity  Microsoft.CodeAnalysis.Editor.TypeScript.ScriptContexts.ITypeScriptProjectProvider

用關鍵字在 TypeScript Github 基地查到可怕的討論串,發生在 TypeScript Tools for VS2015 2.0.2 版,一堆人連 VS2015 都重裝了還搞不定... 在討論串下方發到一線生機,找到另一則討論,有網友找到解決:

1.刪除 %localappdata%\Microsoft\VisualStudio\14.0\ComponentModelCache 資料夾
2.以管理者身分開啟 Developer Command Prompt for VS2015 執行 devenv /setup

如法炮製,終於 VS2015 的 TypeScript 編輯器恢復正常。

當心 Request.Url.ToString() 傳回無效 URL

$
0
0

抓到一個誤用 Request.Url.ToString() 造成的 Bug。

由使用者回報錯誤,追出某段程式擷取 Request.Url.ToString() 加工產生的連結有誤。 原本 URL 參數包含另一頁網址,依規定做了 UrlEncode,如: b=http%3A//blog.darkthread.net%3Fm%3D1%26a%3D456 。ASP.NET 程式抓取 Request.Url.ToString() 修改後串接額外參數"&a=ABC",得到的結果卻是 b=httq://blog.darkthread.net/?m=1&a=456&a=ABC [註: 此處改成 httq 是為防止被當成可連結網址],也因此,接收網頁抓取的 a 變成 456,ABC。

實測後發現,我一直誤認 Request.Url.ToString() 傳回的會是 URL 原始字串(就是瀏覽器地址欄顯示的字串內容),事實不然,來看一個測試:

依據 MSDN 文件

The value returned by this property differs from ToString and AbsoluteUri. ToString returns the canonically unescaped form of the URI. AbsoluteUri returns the canonically escaped form of the URI.

要將 Uri 型別轉為字串,ToString()AbsoluteUriOriginalString都是選項,但意義與用途不同。

Uri.ToString() 傳回的是 URI 以標準方式未逸出處理(Unescaped)過的版本
Uri.AbsoluteUri 傳回的則是 URI 以標準方式逸出處理(Escaped)過的版本
Uri.OriginalString 則是未經處理的原始版本(OriginalString 序列化後不會保留)

換言之,OriginalString 是建構物件時所提供的字串參數,原汁原味連不符規範的部分也都保留;ToString() 及 AbsoluteUri 則為標準化處理過的版本, ToString() 為方便人類閱讀將 %XX 編碼還原回字元,但可能因此不符合 URL 規範,用於瀏覽器可能會出錯(就如本案例); AbsoluteUri 產生的結果則力求符合規範,遺漏 UrlEncode 的部分也會補上,例如以下範例:

var uri = new Uri("http://blog.darkthread.net?a=a c"); //參數空白未編碼 
Debug.WriteLine(uri.AbsoluteUri); //會轉為http://blog.darkthread.net/?a=a%20c

在本案例中,AbsoluteUri 是較佳選擇,而實務上應避免使用 ToString(),以免產生無效 URL。

SQL 最大伺服器記憶體設定值研究

$
0
0

SQL Server 預設會用光主機所有記憶體(預設上限為 2PB = 2048TB),除非整個資料庫容量小於總記憶體,否則把記憶體當成 Cache 能提升效能、減少磁碟 I/O 耗損,絕對利多於弊。如果同主機有多個資料庫執行個體,或是要與其他應用程式、服務分享記憶體,就必須透過 Max_Server_Memory設定伺服器可用記憶體上限,保留一部分供其他程式使用。

最近被問到一個問題,針對專屬資料庫用途的 SQL Server 主機,剩餘記憶體的監控警戒值該怎麼抓?若依通用慣例抓 90%,主機應該 7x24x365 都處於紅色警戒,警告信件簡訊滿天飛,明顯不可行。那麼:針對專屬 SQL 主機,Max Server Memory 該設多少? 記憶體警戒水位該怎麼抓?

關於 SQL 最大伺服器記憶體(Max Server Memory)的估算,我查到 SQL MVP Jonathan Kehayias 一篇常被普遍的指南 - How much memory does my SQL Server actually need- - Jonathan Kehayias

SQL 最大伺服器記憶體基本規則:

總記憶體 4-16GB 時,保留 1GB 給 OS,剩下全撥給 SQL Server。總記憶體超過 16GB 時,每增加 8GB 再多留 1GB 給 OS。

要估許是否能撥更多記憶體給 SQL 可觀察 Memory\Available Mbytes 效能指標,目標是可用記憶體最起碼維持 150-300MB
即可。( 低於96MB Windows 會發警報 LowMemoryResourceNotification,>256GB 的主機建議保留可用 1GB 以上較保險 )
另一版更詳細的計算公式為:

總記憶體 – 執行緒堆疊用量[最大工作執行緒數目 * 堆疊大小(x86 512K、x64 2MB)] – OS保留量(1-4GB) – 其他應用程式保留量 – SQLCLR/Linked Server 等使用量

官方文件也提到類似估算方法:

  • 從 OS 總記憶體中保留 1GB - 4GB 的記憶體給 OS 本身。
  • 減去等於不受 [最大伺服器記憶體] 控制的潛在 SQL Server 記憶體配置,該配置的算法為堆疊大小 1 * 計算得出的最大背景工作執行緒數 2 + -g 啟動參數 3 (若未設定 -g,則預設為 256MB)。 餘數即為單一執行個體安裝的 max_server_memory 設定。

文章冑並提供一個判斷記憶體是不是給太多的方法 – 觀察以下三個效能計數器:

  1. SQL Server:Buffer Manager\Page Life Expectancy
  2. SQL Server:Buffer Manager\Page Reads/Sec
  3. Physical Disk\Disk Reads/sec

若 Page Life Expectancy 持續上升永不下降而 Page Reads/Sec 及 Disk Reads/Sec 偏低,象徵記憶體過剩,可試著減少 Max Server Memory 再觀察指標變化,設法讓 Page Life Expectancy 落入合理值。(依 Jonathan 的建議,PLE合理值 = 資料Cache GB數 / 4GB * 300,每 300 秒平均淘換掉 4GB 的 Cache 內容,時間過短將導致讀寫 IO 偏高)

除此之外,專屬 SQL Server 有時也會拿來跑 SSIS、SSAS,這部分不算在 SQL Server 的記憶體用量,要記得歸類為其他應用程式計算。

最後,回到記憶體警戒水位如何決定。只跑資料庫引擎的 SQL 主機,若無意外會將撥給它用的記憶體吃好吃滿,只留 1GB 給 Windows, 故系統剩餘可用記憶體用量會依 Windows 及其他程式用剩多少而訂,改抓 Memory: Available MBs 指標會比百分比更容易性,依 Janathan 建議可抓 >300MB 以求保險( 256GB 以上的主機抓 1GB ),應是不錯的參考值。

CODE - C# 推算檔案相對路徑

$
0
0

最近在資料夾比對工具遇到一個需求:要以某個資料夾為基準推算檔案的相對路徑。例如,若基準資料夾為 C:\Folder,則 C:\Folder\SubFolder\Test.txt 的相對路徑為 SubFolder\Test.txt。

類似需求以前加減寫過,說穿了全是字串比對的功夫,靠將路徑前方相同部分移除取得相對路徑。如果相對路徑需支援 ..\..\Test.txt 這種寫法,邏輯會複雜一些,若還再考慮英文大小寫的話...

總之,計算相對路徑說難不難,自己寫瑣碎細節倒也不少。於是我想起前幾天談到的 .NET Uri 類別,恰巧有個 MakeRelativeUri() 方法,研究後找到坐享其成的省事解法。

直接用範例程式展示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace UriTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //標準測試
            Test(@"C:\Folder\Test.txt", @"C:\Folder\");
            Test(@"C:\Folder\SubFolder\Test.txt", @"C:\Folder\");
            //上層資料夾
            Test(@"C:\Folder\Test.txt", @"C:\Folder\SubFolder");
            //相鄰資料夾
            Test(@"C:\Folder\SubFolderA\Test.txt", @"C:\Folder\SubFolderB");
            //忽略大小寫差異
            Test(@"c:\folder\subfolder\Test.txt", @"C:\FOLDER\SUBFOLDER");
            //限定basePath子目錄下,不允許上層或相鄰目錄(安全考量)
            try
            {
                Test(@"C:\Folder\Test.txt", @"C:\Folder\SubFolder", true);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
            }
            try
            {
                Test(@"C:\Folder\SubFolderA\Test.txt", @"C:\Folder\SubFolderB", true);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
            }

            Console.ReadLine();
        }

        static void Test(string path, string basePath, bool limitSubfolder = false)
        {
            Console.WriteLine(new string('=', 40));
            Console.WriteLine($"Path: {path}");
            Console.WriteLine($"Base Path: {basePath}");
            Console.WriteLine(
                $"Relative Path: {GetRelativePath(path, basePath, limitSubfolder)}");
        }

        public static string GetRelativePath(string fullPath, string basePath, 
            bool limitSubfolder = false)
        {
            if (!basePath.EndsWith(@"\")) basePath += @"\";
            Uri fp = new Uri(fullPath);
            Uri bp = new Uri(basePath);
            var relPath = bp.MakeRelativeUri(fp).ToString().Replace("/", @"\");
            if (relPath.Contains(@"..\") && limitSubfolder)
                throw new ApplicationException("path must be under basePath!");
            return relPath;
        }
    }
}

結果如下,只需幾行程式搞定,推算"..\"上層目錄、英文大小寫有別都難不倒它。

補充兩個小地方:

  1. Uri 傳回的路徑依循 RFC 規範用"/"作為目錄分隔字元,在 Windows 環境可將"/"置換成"\"以求一致。
  2. 實務上有時會限制檔案必須放在指定目錄下,因此我在 GetRelativePath() 加了 limitSubfoler 參數,以便在跳脫範圍時抛出例外。

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 行為有關。

解決 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(精準到微秒,哈),均速六分速,再下一馬。

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

Viewing all 2311 articles
Browse latest View live