包含標籤 CSharp 的文章

菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧

img

這是俺整理公司新訓內容的第五篇文章,目標是使用三層式架構 (3-Layer Architecture) 來切分服務的關注點和職責

什麼是分層?分層可以吃嗎?

天地混沌如雞子,商業邏輯生其中。

萬八千歲,天地開闢。表現層為天。資料層為地。商業邏輯層在其中……

    --民明書坊《盤古與他的CRUD之旅》

根據民明書坊的文獻記載,我們常聽到的「天地玄黃,宇宙洪荒」云云,其實指的就是上古時期的開發狀況。當時世界還是一片混沌,所有的程式碼都混雜成一坨,不是所有東西寫在一起你儂我儂,一言不合就三千行;就是依賴關係交錯複雜,改了北極壞南極。

要說有多亂呢,大概就算前人嘗試引入了 MVC,也只是改成把所有程式都塞在 Controller 而已,其絕望程度可見一斑。

這時候隔壁課的老盤調過來接刀,一看不得了,便決定先對這屎山整頓一番。他大喝一聲,那些靠近使用者的便上浮起來化作了天,親近資料庫的便沉澱下去變成了地,而所有的商業邏輯就連接著兩者,支撐起了整個專案。這也就是分層架構的由來。

三層式架構

分層架構是運用最為廣泛的架構模式,幾乎每個軟體系統都需要通過層(Layer)來隔離不同的關注點(Concern Point),以此應對不同需求的變化,使得這種變化可以獨立進行;此外,分層架構模式還是隔離業務複雜度與技術複雜度的利器。 -- Ray’s Notes

一般來說,最常見的分層架構就是三層式架構了。

三層式架構顧名思義就是把應用程式分成三層,通常會分成「展示層、商業邏輯層、資料存取層」。

分層架構01

……

閱讀全文


菜雞抓蟲: 使用 FromUri 的複雜型別在有傳遞 QueryString 的情況下會先建立再賦值

Image

事發緣由

在 .net Framework 4.6.2 MVC 的 ApiController 中,某個查詢資料列表的方法除了提供查詢條件的參數以外,還有提供選擇性的分頁參數。也就是像這樣子:

[HttpGet]
public IEnumerable<Boo> GetBoos(
    [FromUri] SearchBooParameter parameter,
    [FromUri] PagingParameter paging = null)
{
    // 呼叫 Service 查資料...
}

由於需要調整該功能的預設排序,改為由大到小,又不想背負更改大量共用的 PagingParameter 去影響到其他使用到的地方,決定在 Controller 這裡簡單用預測值加上判斷處理一下就好

相信著「若使用者沒有傳遞 paging 相關的參數,應該就會是給定的預設值 null 吧!」的我,用了 if (paging is null) 進行判斷:若是 null 的情況就將其中用來標示排序方向的成員 isDesc 設定為 true,開開心心交差。

[HttpGet]
public IEnumerable<Boo> GetBoos(
    [FromUri] SearchBooParameter parameter,
    [FromUri] PagingParameter paging = null)
{
    if (paging is null)
    {
        paging = new PagingParameter();
        paging.isDesc = true; // 預設由大到小
    }
    // 呼叫 Service 查資料...
}

但實際使用之後發現:即使只有傳入查詢條件參數、未傳遞 paging 時,資料仍然由小到大顯示,且 paging.isDesc 竟然是 false,並未被更改到。也就是說,即使未傳遞 paging,它也並不是 null!

實測之後發現:若在呼叫該 API 的時候,給定一個完全無關的參數,例如 ?a=1,則 paging 還是會被建立一個實體出來,並無視 = null 這個預設值。因此就導致了非預期(=跟我想的不一樣啊!)的行為。

這邊直接先講結論:如果有傳遞 QueryString 的任何參數時,不管這些參數跟指定的類別有沒有關係,放在 [FromUri] 的複雜型別都會先建立出實體,再嘗試和 QueryString 的內容進行比對與設值

設定在 [FromUri] 的複雜型別身上的預設值,像是 [FromUri] PagingParameter paging = null 只有完全沒給任何 QueryString 的時候才會吃到。(不過因為預設值只能是常數的關係,基本上就是指 defualt 的 null)

因此如果遇到要給定預設值的場合,還是得乖乖地針對型別中的成員做設定比較保險,例如 bool isDesc { get; set; } = true。另外,因為完全沒給 QueryString 的時候還是會是 null,故該有的參數檢查仍然不能漏了。

……

閱讀全文


C#: BenchmarkDotnet —— 效能測試好簡單

「你寫那什麼鬼東西?這個OOO寫法比較好啦!」
『聽你在屁!明明是這個XXX寫法快= =』

哇喔!等等!想戰效能嗎?那你一定需要這款 BenchmarkDotnet

介紹與安裝

我們在 Coding 的時候,或多或少都會有「不知道這兩個寫法哪個比較好?」、「聽說A寫法比B寫法快,真的嗎?」這類關於效能的疑問。

在遠古時期,當我們需要驗證這種想法,可能就要用記錄秒數的方式,或是搭配迴圈、然後再印在畫面上等等這類土法煉鋼的方式。

然而這種單純計秒數的 Print 流測試,可能比較到了時間成本,卻忽略了吃掉的記憶體這些空間成本;又或是每次都要插一堆列印文字的語句,因為麻煩就萌生退意等等…

這時候就是 BenchmarkDotnet 出場的時候啦!

BenchmarkDotnet 是一款簡單好用的效能比較工具,可以幫助我們比對多組程式碼,並告訴我們平均的執行時間、耗用的記憶體等等。

只要使用 BenchmarkDotnet 這個神奇妙妙幫手,它就能幫我們搞定這些麻煩的事情,讓我們可以專注在要測試的程式碼內容囉。

……

閱讀全文


菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧

img

這是俺整理公司新訓內容的第四篇文章,目標是簡單地使用 Swagger 工具來自動產生可互動的 API 文件

API 文件與 Swagger

上一篇 我們建立了一個有簡單的 CRUD 的 Web API 服務,這篇我們就接續著 API 服務往下看吧!

之前我們介紹 API 的時候有提過:API 是為了讓兩個服務之間可以溝通、互動所產生的接口。而所有的溝通要有效,都一定要先有共識,隨著溝通的人數越來越多,或是內容的理解要越來越細,就會用文件或契約的方式來達成共識。

回到我們的 API 服務開發來說,就是你除了把服務生出來了,可以跑了以外,還有一個重要的點是:必須讓所有的使用者(包含幾個月後的你自己)知道怎麼使用這組 API 服務

也就來說,就是要寫 API 規格文件 啦!

……

閱讀全文


菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧

Image

這是俺整理公司新訓內容的第三篇文章,目標是在 .NET Core 簡單地使用 Dapper 連線到資料庫並完成 CRUD 的功能

接續 上一篇 的進度,我們接著要來連線到資料庫中完成我們的 Web Api 的 CRUD 範例。因為從新訓時期到現在工作團隊作業上主要都是使用 Dapper 來做連線資料庫的工作,這邊就直接用 Dapper 來推進吧!

Dapper 有多好用呢?它輕量、它簡單、它快速。總之先把大神們的介紹文直接拿來鎮樓:

那麼按照慣例,我們先來 吹捧今天的主角 說明一點簡單的前因後果吧。想直接實作的朋友,可以跳到正式開工的小節呦。

……

閱讀全文


菜雞新訓記 (2): 認識 Api & 使用 .net Core 來建立簡單的 Web Api 服務吧

Image

這是俺整理公司新訓內容的第二篇文章,目標是對 Api, Restful Api, HTTP 等相關的知識點做個筆記,並用 .net Core 建立一個簡易的 Web Api 專案

前言、基本觀念

我們在 上一篇 記錄了新訓第一天的 Git 操作筆記。接著在這篇,我們終於要進入 .net Core 啦!

目前的規劃是先從建立一個可以使用的、最簡單版本的 Web Api 服務開始,再將各個工具擴增進來。所以後續的文章應該都會以這篇的簡易 API 為基底繼續延伸下去(如果順利的話啦)

這篇文章的前半段會用來記錄一些使用或開發 API 常用到的相關知識,如果對 HTTP 的部分已經有點頭緒,或是迫不及待想直接動手用 .net Core 開 Api 服務的朋友們,可以直接跳到 正式開工 的部份。那麼,我們開始吧~


什麼是 API

我們在物件導向的 介面 時有稍微聊過所謂介面(Interface)的概念:「在兩個系統,或是兩個分層之間要介接的時候,只需要提供我這個功能的接口/介面給對方,就能讓對方知道如何使用」

API(Application Programming Interface)也是同樣的道理:

在不同的應用程式或服務(Application)之間,使用程式碼(Programming)的方式提供一組 介面(Interface),讓提供方和使用方可以藉由這組介面銜接起來。

API 最貼切的比喻就是我們在 封裝篇 也用過的販賣機:販賣機會提供不同飲料的按鈕,當我們選擇了其中一個按鈕按下、投了錢之後,對應的飲料就會掉下來。

對應回來就是:我們到了某個服務(販賣機),去拿我們想要的資料(飲料),所以呼叫了該服務的某支 API(按鈕)並且提供了一些該 API 要求的資料(投錢),最後 API 就會把我們想要的資料交給我們(飲料)

再用更實際的例子來說就像是:假設我們想要做一款可以查詢台北市的公車動態的 APP,於是我們到了提供公車動態的服務 MOTC Transport API v2 去找我們想要的 API,過程中我們可能需要告訴服務我們要查的是台北市,最後服務就會將公車動態的資料交給我們。

關於 API 的部份,推薦可以先閱讀過 Huli 大大的這兩篇,將基本觀念說明的相當好懂且透徹:

另外,也推一下我在 CodingBar 看到的這篇 API 到底是什麼? 用白話文帶你認識 和它所引用的影片:

……

閱讀全文


菜雞抓蟲: 在 Amazon Linux AMI 安裝 .net Core 時卡在 Requires: openssl-libs

最近遇到在 Amazon Linux AMI 要安裝 .net Core 3.1 環境的時候,會一直跳出
Requires: openssl-libs 而無法安裝的問題,儘管明明已經有 openssl 了,但還是解析失敗找不到依賴,過程一直碰壁,因此在這邊紀錄一下。

過程中嘗試了安裝 openssl-libs(會找不到該套件)、下載 Dotnet 的 tar.gz ,再直接對執行檔下 Dotnet 指令起站台(雖然網站起得來,但執行者會是當下的登入身分,也就是 ‘’@連線進來的IP-伺服器位置,而非由本機執行。後續如果有連線資料庫等檢查權限的地方就很容易出錯)

最後在 Dotnet Core 的 issue 翻到這篇 Cannot install .NET Core 2.0 on Amazon Linux AMI 才成功解決。

首先先將 openssl-libs 的 SPEC 抓下來,然後給 RPM 建置一下。這兩句可以參考一下這篇 RPM 打包︰由一竅不通到動手濫用 (二) 的說明。

wget https://github.com/dotnet/core/files/2186067/openssl-libs-ami.spec.txt
rpmbuild --bb openssl-libs-ami.spec.txt
……

閱讀全文


C#: 元組 (Tuple)

因為隔壁介紹原則的部分有點卡住了,所以這週來紀錄一下挺常用到的方便東西:Tuple

這篇的 Tuple 指的是 C# 7.0 後提供的 ValueTuple 和相關語法,舊版得用 Tuple.Create 建立,成員的名稱也只能使用 Item1, Item2…,實用性並不是很高。但新 Tuple 出現後,方便程度大大提升,這邊就稍作紀錄一下。

註:此處使用的 Dump 是 Linqpad 提供的輸出方法,把它當成 Print 就行了。

var student = (1, "王小明");
student.Item1.Dump(); // 1
student.Item2.Dump(); // 王小明

student.Dump();

可以看到 Tuple 的建立相當簡單,只需要用小括號 () 括選起來即可。建立後的內容就會像這樣:

但這樣使用就和之前一樣,取出來時只能拿 Item1, Item2,放個幾天根本就不記得 Item1 裡面是啥東西了。這時我們就可以替成員們取名字

(int ID, string Name) student = (1, "王小明");
student.ID.Dump(); // 1
student.Name.Dump(); // 王小明

如此使用的時候就和一般操作物件的習慣沒有差別,也增加了可讀性。

……

閱讀全文


C#: 時區轉換、民國西元、國曆農曆、中文月份週期

聊到將時間從 UTC 轉到台灣時間,居然還是聽到朋友表示使用 +8 小時的做法,驚為天人。這種做法可能會造成後續的問題,例如時區並不會跟著變動,或是遇到日光節約等特殊狀況就容易出事。和西元民國轉換直接 -1911 一樣不穩定。

這篇就用來記錄一下之前看過比較優雅的時區轉換方式,順便將先前存著的時間處理相關資料整理一下,方便之後需要時可以馬上回來查詢。

TimeZoneInfo: 時區資訊

轉換方式主要參考自 [食譜好菜] DateTime 具有文化特性的格式化及時區的轉換在各時區間轉換時間,感謝前人的指引。

關於文化特性,也可以參考本站的 菜雞抓蟲: DateTime.ToString() 之我們不一樣 & CultureInfo 文化特性小筆記 呦。

// 假設現在是要從標準時區 +00:00 轉換到台灣時區,故這邊使用 UtcNow 先取標準世界協調時間
var nowDateTime = DateTime.UtcNow;

nowDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 15:56:05 +00:00

// ==================================================

// 傳統的 直接對時間做計算的方式…
var addedDateTime = nowDateTime.AddHours(8);

addedDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 23:56:05 +00:00
// 可以看到儘管時間變動了,時區仍然還在 +00:00

// ==================================================

// 使用 TimeZoneInfo 先取得台北時區
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");

// 再使用 TimeZoneInfo 來變更時間
var convertedDateTime = TimeZoneInfo.ConvertTime(nowDateTime, timeZone);

convertedDateTime.ToString("yyyy/MM/dd H:mm:ss zzz").Dump();
// 2020/08/30 23:56:05 +08:00
// 可以看到除了時間變更以外,時區也切換到 +08:00 了!

上面取得台北時區的步驟,可以參照 Time Zone IDs 來查詢想要的時區。這樣的時區切換方式,不僅副作用少,不會因為時區沒轉雷到後續接手的人,也省卻了擔心日光節約等等問題,這種事就交給微軟去煩惱吧!

……

閱讀全文


C#: 字串插值 (String interpolation) 的格式化

自從 C# 有了 字串插值 這東西之後,我就一直是愛用者。畢竟比起 string.format 這東西可是看起來優雅多了。例如:

var message = $"哈囉,{userName} 您的點數將於 {cutoffTime} 到期。";

簡潔又明瞭,一眼就能理解字串內容。實在是挺方便,後來發現這東西還有一些延伸用法,這邊就稍加紀錄一下:

字串插值中能夠做簡易計算,例如:

var message = $"您輸入的數值為:{a}、{b}。他們相加為:{a + b}";

同時,在字串插值時可以針對內容作格式化,只需要用 : 來區隔,妥善運用可以省下一堆 ToString() 的空間。

例如當我們要將時間格式化的時候,就可以:

var date = new DateTime(2020, 8, 9);
var message = $"您的商品已於 {date:yyyy/MM/dd} 抵達。";
// 您的商品已於 2020/08/09 抵達。

另外,數值當然也可以格式化,不過數值的應用比較複雜,主要是用來定下小數點、百分比等符號的位置。 可以參見 自訂數值格式字串 - Microsoft Docs

var cost = 2100;
var message1 = $"您的商品一共是 {cost:#,###} 元";
// 用 # 可以替數字預留位置
// 您的商品一共是 2,100 元

var message2 = $"您的商品一共是 {cost:#,###.00} 元";
// 也可以用 0 來預留位置,若該數字有值就會顯示該數字,沒有就會自動補 0
// 您的商品一共是 2,100.00 元
……

閱讀全文