顯示具有 C# 標籤的文章。 顯示所有文章
顯示具有 C# 標籤的文章。 顯示所有文章

重構:改善 .NET 與 C# 應用程式的設計,償還欠下的技術債-審校序

重構:改善 .NET 與 C# 應用程式的設計,償還欠下的技術債-審校序

2023年12月,正忙於年末最後一場iThome - Kubernetes Summit 2023的演講,心想,忙完這一場終於可以好好休息一下。但事情永遠不是傻子所想的那麼簡單。突然,信箱進來一封編輯邀約的信件,正想說,如果是翻譯工作,就再幫忙找其他新鮮的肝(新譯者),結果是翻譯也不是翻譯。出版社嘗試用了ChatGPT進行初譯,然後由專業編輯與譯者進行修潤與審校,想嘗試這樣的合作模式。快速瀏覽了主題與內容,我非常有興趣,立即回信說,我想接下這份工作。

答客問:多層JSON結構如何轉換為C#的Model物件

答客問:多層JSON結構如何轉換為C#的Model物件

教學上,我一直認為沒有爛問題。只有懂與不懂。

平常我們在課程上設計的範例為求簡單好懂,通常都會盡量簡化。但回去之後的實務問題,在我眼裡都非常有價值。API 的開發可以很簡單,也可以很複雜,例如,只是單純從後端資料庫取資料,資料表與Model的對應關係通常都是一對一(1:1)的,輸出入也很單純只會有一層,除非你導入了如 Entity Framework 之類 ORM,產生了一對多(1:n)或多對多(n:n)的情境。

{
    "filter":{
        "name":"bruce",
        "currentPage":1,
        "pageSize":5
    }
}

以同學提問的情況,感覺比較像 Front-End First(或稱 JSON First),也就是 JSON 規格是由前端定義。我們一步一步來解決。

C#-寫一個 Retry 機制程式碼

C#-寫一個 Retry 機制程式碼

平常,Web 比較多人問關於「時間」的問題是「排程」,在課程被問到「Retry」機制,想想,真的托開發微信API的福,我還真的寫過那麼一小段 Retry 機制的程式碼。完整程式碼沒幾行,就說點小故事,拉點畫面行數吧。

當時我們與微信的測試主機是放在 Azure 東亞(香港)機房,不過因為大陸特別的網路關係,每每從下班之後就變得不穩定,我們某個 API 需要收到對方很明確的回應(成功-失敗)訊息才能往下進行。但好死不死,大陸在下班至午夜的國際網路相當不穩定,以至於我們的 API 常常在下班|半夜被搞死,那段時間,半夜看著Slack的Log偵錯,更是家常便飯。找了好久,才確定那支 API 不是問題點。但問題點來了,對方不知是有意或無意 loss 非內陸的請求,我們知道,把正式機放到 China Azure 就能解決特別的網路問題(後來我們正式機放進去了,還是一堆雷 >__< )。有無更好的解法?想了很久,我決定寫一個 Retry 機制。

還在徒手揮汗寫For測效能,閃開讓BenchmarkDotNet來

還在徒手揮汗寫For測效能,閃開讓BenchmarkDotNet來

上集使用反射執行方法的7種方式我們談到寫法不同,效果不同這件事,也說到,通常我們測試程式效能會很習慣的採用 Stopwatch 模式(Stopwatch Pattern)來進行程式執行效率的比較。

Stopwatch sw = new Stopwatch();
sw.Start();
// stuff
sw.Stop();

我們把前面的 7 Ways 程式碼用 for 跑 20000 次計算時間,可以得到以下結果:

Way1 Time: 40 ms
Way2 Time: 40 ms
Way3 Time: 76 ms
Way4 Time: 67 ms
Way5 Time: 148 ms
Way6 Time: 4233 ms
Way7 Time: 2054 ms
  

但這樣對嗎?在談效率時,其實有二種意義:時間空間(文言文:時間複雜度空間複雜度),我不是來教書的,雖然 Stopwatch 寫起來不複雜,但僅能看到時間單一面向,好像只要快就是好,怪怪的。

那有無更好的選擇?

Reflection-使用反射執行方法的7種方式

Reflection-使用反射執行方法的7種方式

最近因為專案需求,努力寫著反射(Reflection)程式,還好哥早在2011年就打過底,但大型實務應用開發之後在 fb 上發表些小心得:

  • 取得物件 System.Type 後基本上就無敵了。
  • 你能做的、你不能做的,Reflection 都能做。白話:一些簡單的 private 之類限制對 Reflection 無效。
  • 製作沒有規範 Assembly 的 Reflection 解析執行程式需要:細心、耐心與草x馬的愛心。(突然覺得那些反編譯器好偉大)
  • 接上題,規範好 Assembly 介面定義,大家都會很開心
  • 寫法不同,效果不同。純反射 4.1 > dynamic 1.4 < delegate 1.2。delegate 最快。
  • Expression 網路上的資料它也比 dynamic 快,但我的實作不適合驗證,沒取得數值。
  • ps. 美國微軟工程師的 Bruce 說,dynamic 能不用就不用。我自己的實作心得:dynamic 沒有比 delegate 快,又有些缺點,我也不建議使用。

後來,實在手癢的受不了,把底層用 Expression 改寫,得到以下結果:

  • v1, reflection - 純反射,最慢;
  • v2, delegate - 比純反射快 4 倍(自己測);
  • v3, expression - 與 delegate 差不多;

速度與可讀性而言,目前 delegate 最好。寫法不同,效果不同。

Reflection - Method Invoke 7 Ways

這裡我將手邊知道的「執行方法」的方法做了以下整理,總共有 7 種方向。

Azure Table Storage API 泛型設計

Azure Table Storage API 泛型設計

最近有機會使用到Azure Table Storage服務,Table Storage是早期Azure儲存體一種NoSQL儲存方式,程式開發上花點時間看看官網的文件,跟著範例做一下,大致上不會有太大問題。不過等真的要使用時會發現,如果按照範例程式的寫法,基本上是一整個寫死,沒有彈性可言。

建立Entity(實體)

Table Storage 的 SDK 設計是你在呼叫 Table API 之前必須傳入要儲存的 Entity(實體),如果讀者熟悉 Entity Framework 的話,可以想成 Entity Framework 的 Entity,如官網範例的 CustomerEntity

 public class CustomerEntity : TableEntity
 {
     public CustomerEntity(string lastName, string firstName)
     {
         this.PartitionKey = lastName;
         this.RowKey = firstName;
     }

     public CustomerEntity() { }
     public string Email { get; set; }
     public string PhoneNumber { get; set; }
 }  
 

然後進行CRUD操作時都是以 Entity 為單位,例如查詢:

 // Create a new customer entity.
 CustomerEntity customer1 = new CustomerEntity("Harp", "Walter");
 customer1.Email = "Walter@contoso.com";
 customer1.PhoneNumber = "425-555-0101";

 // Create the TableOperation object that inserts the customer entity.
 TableOperation insertOperation = TableOperation.Insert(customer1);

 // Execute the insert operation.
 table.Execute(insertOperation);  
 

在正式使用碰到的問題就是,我們通常針對這類操作的 API 會先抽離出來給大家共用,但問題是,此 Table Storage API 並不會事前知道你所要操作的 Entity 格式,Entity 格式應是留給使用端去定義,這樣才能發揮 NoSQL 的長處,如果一開始定義寫死了,那就和關連式資料庫沒有差別了。

.NET Framework 4.6新增Unix TimeStamp方法

.NET Framework 4.6新增Unix Time Stamp

skilltree課程

Timestamp(中文稱時戳、時間戳)是經常用於安全保護上的一種機制,例如每天上下班的打卡,就是一種保護勞資雙方的機制,卡打下去的那一刻的時間點是無法被偽造,用計算工時與工資。在網站資訊安全方面,例如近年流行的OAuth認證機制(facebook登入、google登入、github登入、Microsoft account登入…等第三方登入機制)中重要的Token,通常就會帶有Timestamp機制,以驗證Token的時效性。

Unix Time Stamp

TimeStamp很好,但他的格式並不好處理,我們看一下wiki上的範例:

  • Tue 01-01-2009 6:00
  • 2005-10-30 T 10:45 UTC
  • 2007-11-09 T 11:20 UTC
  • Sat Jul 23 02:16:57 2005
  • 12569537329
  • (1969-07-21 T 02:56 UTC) – first footstep on the Moon, "That's one small step for man, one giant leap for mankind"
  • 07:38, 11 December 2012 (UTC)

TimeStamp格式那麼多之外,在多國語系下,你可能還要自行先轉換為UTC (格林威治標準時間)時間。

在上述格式中,我們特別注意到一個12569537329數值,這是什麼東西?這種格式稱Unix Time,它是一種把時間轉換為數值的方式,它以1970-01-01 T 00:00 UTC為基準,每秒加 1 方式計算,如果是在基準點之前,就每秒 -1 方式計算。例如,我們把上述 Unix Time 放到http://www.unixtimestamp.com/進行轉換:

Timestamp Converter

就能還原成特定的時間點。因為數值的特性直覺、簡單、好處理,所以 Unix time 常被拿來做 Timestamp 使用。

如何於MVC/Web API路由中傳送Base64編碼

如何於MVC/Web API路由中傳送Base64編碼

base64
from technet.microsoft.com

之前提到過如何使用路由傳遞含"+"符號到ASP.NET Web API 2有二種方式,一是使用QueryString,一是修改allowDoubleEscaping,不過allowDoubleEscaping的修改會降低系統安全性,是個不得以的選項。重覆的問題又活生生出現在面前,只是這一次必須把這個含+符號的值放在路由中。怎麼辦?有無更好的方式解決這個問題呢?

+號+號你為什麼會出現

首先,第一次處理如何使用路由傳遞含"+"符號到ASP.NET Web API 2問題時,那是一段對稱加密的程式,因為是中間接手處理,沒有細看實作細節,我們以DESCryptoServiceProvider (DES 的實作)為例來說明:

 private string Encryption(string plainText)                
 {                                                                      
     if (plainText == null || plainText.Length <= 0)                    
         throw new ArgumentNullException("cipherText");

     byte[] b = Encoding.UTF8.GetBytes(plainText);                      
     DESCryptoServiceProvider des = new DESCryptoServiceProvider();     
     ICryptoTransform ict = des.CreateEncryptor(des.Key, des.IV);               
     byte[] desData = ict.TransformFinalBlock(b, 0, b.Length);
                                                                        
     return Convert.ToBase64String(desData);                            
 }                                                                       
 
DES加密程式

看出問題點了嗎?

我們先看看這個的字串如果使用在路由上會產生什麼問題,以/{controller}/{action}/{id}路由為例:/home/index/EZ7+/+wvla4=,怎麼+號(加號)都還沒處理就馬上出事了?

看的出來了嗎?

有在寫MVC或Web API的人,第二個問題很直覺就能看出來了,多了一個「/」符號,此時就算設置allowDoubleEscaping讓路由能正常處理+號,id透過ModelBinding後還是不正常的。

回到問題一,看出問題了嗎?