<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Posts on 伊果的沒人看筆記本</title>
    <link>https://igouist.github.io/post/</link>
    <description>Recent content in Posts on 伊果的沒人看筆記本</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-Hant-TW</language>
    <managingEditor>Igouist (Igouist)</managingEditor>
    <webMaster>Igouist (Igouist)</webMaster>
    <follow_challenge>
      <feedId>56200764111934464</feedId>
      <userId>41821085092905984</userId>
    </follow_challenge>
    <lastBuildDate>Sat, 28 Feb 2026 21:00:00 +0800</lastBuildDate><atom:link href="https://igouist.github.io/post/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>鴛鴦鍋就是要分成涮肉區和煮湯區才符合單一職責吧？</title>
      <link>https://igouist.github.io/post/2026/02/split-pot-and-srp/</link>
      <pubDate>Sat, 28 Feb 2026 21:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2026/02/split-pot-and-srp/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;這篇是我的 &lt;a href=&#34;https://blogblog.club/party/&#34;&gt;2026 年 2 月份 BlogBlog 同樂會&lt;/a&gt; 投稿，本月主題是「&lt;a href=&#34;https://wiwi.blog/blog/blogblog-party-feb-2026/&#34;&gt;只有我這樣嗎？&lt;/a&gt;」。如果你有自己的部落格，歡迎一起來參加！&lt;/p&gt;
&lt;p&gt;如果你跟我一樣想看其他朋朋的文章看到爽，可以到：&lt;a href=&#34;https://wiwi.blog/blog/blogblog-party-feb-2026&#34;&gt;BlogBlog 同樂會：只有我這樣嗎？&lt;/a&gt; 底下的「已經投稿的作者清單」，有各式各樣的優質文讓你三餐配飯、一次滿足。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;參加了「只有我這樣嗎？」的投稿之後，發現原來真的不只我這樣！&lt;br/&gt;
感謝 Shuo-jen 的這篇〈&lt;a href=&#34;https://shuojen.com/blog/2026/03/01/hotpot/&#34;&gt;火鍋界的「微積分大戰」&lt;/a&gt;〉&lt;br/&gt;
在追求鴛鴦鍋最佳實踐的路上，我們都不孤單，歡迎更多朋友加入我們！&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;看到這個月 BlogBlog 同樂會的主題「只有我這樣嗎？」的當下，文章主題就已經確定了。&lt;/p&gt;
&lt;p&gt;畢竟曾經在現實生活問過一圈，完全沒有遇到過同樣做法的朋友，當時的想法就真的是「哇，不會只有我們這樣吧？」如此如此，正好寫文一篇，順便推廣推廣。&lt;/p&gt;
&lt;p&gt;雖然很想直奔主題，但就像系統設計常說的「脫離問題脈絡、直接討論解決方案，就是耍流氓」，所以讓我們從一些背景脈絡開始聊聊吧。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;首先，標題的鴛鴦鍋有一個重要的前提：吃到飽。&lt;/p&gt;
&lt;p&gt;認識我的朋友都知道我是一個無肉不歡的人，凡是聚餐必定要大口吃肉、只要進食就一定要大口吃肉，反正就是要大口吃肉。因此我和吃到飽餐廳的關係，就像斷頭台的刀刃和路易十六的脖子，是一場命運般的奔赴：我不是正在吃到飽餐廳，就是正在前往吃到飽餐廳的路上。&lt;/p&gt;
&lt;p&gt;而在數以百計的吃到飽餐廳種類中，火鍋又是金字塔的頂端，在煮沸的高湯涮上幾秒鐘，就有熱騰騰香噴噴的肉可以品嘗，從「肉量／每秒」為單位的進食效率來看，可以說是沒有比火鍋更方便的了。連續涮上幾片肉，裹上一些蔥蒜醬料，咬下的瞬間，肉汁和奶香味充盈鼻腔，恍惚間彷彿回到遠古時代的高原上跟野人祖先們一起撕咬獵物，這種感覺實在令人欲罷不能。&lt;/p&gt;
&lt;p&gt;為了追求這份歡愉，我踏過了小蒙牛、新馬辣、向辣等戰場，用脂肪累積著戰士的勳章。但是，在這條偉大的火鍋征途上，仍然有一片巨大的陰影，那就是&lt;strong&gt;湯&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;經常吃吃到飽火鍋的人都知道：只要涮的肉多了，煮到後面湯上面都有一層油，而且浮沫四溢、雜質四散，通常煮到這個程度，這鍋湯也就廢了。&lt;/p&gt;
&lt;p&gt;原本該是火鍋主角之一的湯，就這樣淪為了龍套中的龍套，只為了把肉燙熟而服務。尤其像大學生聚餐這種眾菜拱肉的場合，更是連給旁邊火鍋料提鞋兒也不配，什麼湯頭帶來的濃厚、甘甜、溫暖和高普林，根本沒人在乎。散場的時候，也就只留下一灘陰暗扭曲的雜質鍋，跟離職前輩留下來的程式碼一樣，可以說是悲劇中的悲劇，實在令人唏噓。&lt;/p&gt;
&lt;p&gt;但如果、如果我們既要又要－－既想大口吃肉，又想大口喝湯，該怎麼辦呢？&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;某天，我那聰明的女友靈機一動，想出了這套計畫（註：她非常強調要在文章中註明她是發想人。這很合理，畢竟是可以匹敵諾貝爾火鍋獎的殊榮）&lt;/p&gt;
&lt;p&gt;在提供鴛鴦鍋的吃到飽火鍋店裡，服務生會請你們選擇兩個湯底——但這其實是一個陷阱。&lt;/p&gt;
&lt;p&gt;我們要做的就是：&lt;strong&gt;兩邊都選擇同一個湯底&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：店員可能會露出疑惑的表情，不要管他，貴在堅持。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;並且在最初的點餐中，&lt;strong&gt;集中火力在煮湯的底料：番茄、南瓜、玉米等&lt;/strong&gt;，確保兩邊都各有一份（畢竟肉還是要有點湯味才好吃），份量因人而異。&lt;/p&gt;
&lt;p&gt;接著，&lt;strong&gt;請選定一邊作為涮肉專區，另一邊則為煮湯專區&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所謂「同樣的條件、不同的經歷，就是不同的人生」我們將在火鍋實踐這一點。顧名思義，涮肉專區將專門用來涮肉，以確保參與者每分每秒都有美味的肉可以享用。&lt;/p&gt;
&lt;p&gt;而煮湯專區的壓力就更大了，我們需要&lt;strong&gt;大量加入蛤蠣、扇貝等能讓湯頭鮮甜的海鮮，並且看情況運用白菜、香菇等工具來進行調整&lt;/strong&gt;，偶而也要加入一兩片肉來增添風味。&lt;/p&gt;
&lt;p&gt;只要遵守這個簡單的原則：「讓涮肉的歸涮肉、讓煮湯的歸煮湯」在一段時間的熬煮後，我們就能一邊攝取足夠的肉品，一邊享用由海鮮和蔬菜煮好的湯，並在這趟肉和湯的火鍋之旅中，找到兩者之間的平衡。&lt;/p&gt;
&lt;p&gt;而在這趟旅程的結束、吃飽喝足的時候，回頭看看同一組湯頭所煮成的兩鍋湯，那又是另一番體悟了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;讓我們回顧這個方案在系統設計層面上的取捨：首先，全面涮肉之所以會毀壞湯底，是因為涮肉的副作用太大，而食材處理區域之間又太過耦合，以至於相互影響。這是典型的技術債，是因為模組拆分不當所造成的。&lt;/p&gt;
&lt;p&gt;因此，我們會先想到的做法就是進行隔離。但要如何隔離呢？在&lt;a href=&#34;https://zh.wikipedia.org/zh-tw/%E6%B0%91%E6%98%8E%E6%9B%B8%E6%88%BF&#34;&gt;民明書坊&lt;/a&gt;的《鴛鴦鍋設計模式》提到過：常見的方案是使用「辣」與「不辣」來進行劃分。但經過測試，這並不能解決我們的問題。&lt;/p&gt;
&lt;p&gt;幸好，&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle/&#34;&gt;單一職責原則&lt;/a&gt;教過我們：「&lt;strong&gt;每個類別應該只對一個角色／業務場景負責&lt;/strong&gt;」我們要找到的就是場景之間的區別；而吃肉吃到飽，跟喝火鍋湯喝到中風，明顯是不同的需求場景，所以我們應該將處理肉和處理湯的模組進行隔離。&lt;/p&gt;
&lt;p&gt;經過我們火鍋架構師團隊的一番重構，現在湯頭已經比程式碼還清晰，這篇文章的字數也水得差不多了。那麼，以上就是同時懷著「只有我這樣嗎？」和「哈！只有我這樣吧！」心情所釋出的獨門吃到飽煮鍋秘方，祝各位吃鍋順利，大吉大利！&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>菜雞抓蟲：AutoMapper 出現奇怪的型別對應錯誤嗎？貼心的慣例式映射可能正在造訪你家</title>
      <link>https://igouist.github.io/post/2026/02/automapper-flattening/</link>
      <pubDate>Tue, 24 Feb 2026 21:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2026/02/automapper-flattening/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1771938629037.webp&#34;
  alt=&#34;&#34;width=&#34;600&#34; height=&#34;800&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;部門朋朋前陣子忙著把一卡車陳年專案升級到 .Net 8，原本吃著火鍋唱著歌、升級升得好好的，眼看三下五除二就要升級完畢，但就在這一片祥和安寧之中，突然有個專案升級後 API 就原地死去，只留下 AutoMapper 的錯誤訊息，兇案調查也就此開始……&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;俗話說得好：不看錯誤訊息的偵探不是好偵探。&lt;/p&gt;
&lt;p&gt;讓我們先看看以下片段（為保護當事欄位，已做混淆處理）：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;---&amp;gt; AutoMapper.AutoMapperMappingException: Error mapping types.

Mapping types:
BooDataModel -&amp;gt; BooDto
NiceProject.Repositories.DataModel.BooDataModel -&amp;gt; NiceProject.Services.Dto.BooDto

Type Map configuration:
BooDataModel -&amp;gt; BooDto
NiceProject.Repositories.DataModel.BooDataModel -&amp;gt; NiceProject.Services.Dto.BooDto

Destination Member:
ProductOrder

---&amp;gt; AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.

Mapping types:
OrderedEnumerable`1 -&amp;gt; Int32
System.Linq.OrderedEnumerable`1[[Product...]] -&amp;gt; System.Int32
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;對第一現場進行勘驗，發現幾個值得注意的點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;來源類別並沒有 ProductOrder 這個欄位，但目標類別有&lt;/li&gt;
&lt;li&gt;MapperConfiguration 並沒有明確要求 Ignore ProductOrder，但原本的版本運行得好好的，升級 .Net 後才出錯&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;現在我們已經收集了案發現場的線索，但我們還需要回答一個關鍵的問題：錯誤訊息中的 AutoMapper 在案發時究竟做了什麼？&lt;/p&gt;
&lt;h3 id=&#34;automapper-的貼心-flattening&#34;&gt;AutoMapper 的貼心 Flattening&lt;/h3&gt;
&lt;p&gt;要繼續說明下去，就要先從 AutoMapper 的一些貼心小舉動開始說起。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.automapper.io/en/stable/index.html&#34;&gt;AutoMapper&lt;/a&gt; 作為型別轉換工具，最吸引人的賣點就是「&lt;strong&gt;基於慣例的映射（Convention-based mapping）&lt;/strong&gt;」這代表它預先處理了一些常見的情境，讓我們可以無腦使用。例如從 &lt;code&gt;FooName&lt;/code&gt; 直接映射到 &lt;code&gt;fooname&lt;/code&gt;（自動處理掉大小寫問題）、或是某些型別會自動幫忙轉型等等。&lt;/p&gt;
&lt;p&gt;這些慣例在大多時候是很實用的，而且也非常省事。但在一些情況可能就會變成未爆彈，例如在 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper/&#34;&gt;之前的 AutoMapper 筆記&lt;/a&gt; 介紹過的「2.65 變成 3」之謎：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Boo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Foo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Foo Sut()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; boo = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Boo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;2.65&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; cfg.CreateMap&amp;lt;Boo, Foo&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; foo = mapper.Map&amp;lt;Foo&amp;gt;(boo);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; foo; &lt;span style=&#34;color:#75715e&#34;&gt;// 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這次要介紹的就是 AutoMapper 的貼心慣例之一：&lt;strong&gt;Flattening&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you configure a source/destination type pair in AutoMapper, the configurator attempts to match properties and methods on the source type to properties on the destination type. If for any property on the destination type a property, method, or a method prefixed with &amp;ldquo;Get&amp;rdquo; does not exist on the source type, AutoMapper splits the destination member name into individual words (by PascalCase conventions).&lt;/p&gt;
&lt;p&gt;當您在 AutoMapper 中配置來源/目標類型對時，配置器會嘗試將來源類型上的屬性和方法與目標類型上的屬性進行匹配。如果目標類型的任何屬性在來源類型上不存在相應的屬性、方法或以 &amp;ldquo;Get&amp;rdquo; 為前綴的方法，AutoMapper 會將目標成員名稱拆分為單獨的單詞（根據 PascalCase 規範）。&lt;/p&gt;
&lt;p&gt;（&lt;a href=&#34;https://docs.automapper.io/en/stable/Flattening.html&#34;&gt;Flattening — AutoMapper documentation&lt;/a&gt;）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;簡單來說，&lt;strong&gt;AutoMapper 在找不到目標的時候，會嘗試把屬性名稱拆成幾個單字，去抓看看有沒有符合的來源&lt;/strong&gt;。例如 &lt;code&gt;ItemId&lt;/code&gt; 就會拆成 &lt;code&gt;Item&lt;/code&gt; 跟 &lt;code&gt;Id&lt;/code&gt;，再去來源找看看有沒有這兩個東西。&lt;/p&gt;
&lt;p&gt;用一組 LinqPad 腳本能更直觀理解這個動作：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; SourceItem Item { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SourceItem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Target&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ItemId { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; source = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Source
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Item = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SourceItem
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Id = &lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 準備了 source.Item.Id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; cfg.CreateMap&amp;lt;Source, Target&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; target = mapper.Map&amp;lt;Target&amp;gt;(source);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target.ItemId.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// target.ItemId = 256, 從 source.Item.Id 抓來的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在我們已經知道了 AutoMapper 會幫忙試試看組裝欄位來兜出一個結果，跟我那個嘗試把夢到的東西跟樂透號碼關聯起來的鄰居一樣。但是在來源型別結構多層又複雜，我們又想賦值到扁平化後的型別的時候，Flattening 還是蠻有用的。&lt;/p&gt;
&lt;p&gt;那麼，這個好棒棒慣例跟這次的案情又有什麼關係呢？讓我們看看一個轉型失敗的例子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; SourceItem Item { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SourceItem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Target&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ItemId { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; source = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Source
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Item = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SourceItem
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Id = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NaN&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// Not a Number 噠！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; cfg.CreateMap&amp;lt;Source, Target&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; target = mapper.Map&amp;lt;Target&amp;gt;(source);  &lt;span style=&#34;color:#75715e&#34;&gt;// 爆炸！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target.ItemId.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Error mapping types.

Mapping types:
Source -&amp;gt; Target
UserQuery+Source -&amp;gt; UserQuery+Target

Type Map configuration:
Source -&amp;gt; Target
UserQuery+Source -&amp;gt; UserQuery+Target

Destination Member:
ItemId
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;沒錯，錯誤訊息跟我們開頭看見的非常相似，都是型別映射錯誤！&lt;/p&gt;
&lt;h3 id=&#34;走向破案&#34;&gt;走向破案&lt;/h3&gt;
&lt;p&gt;現在我們已經有了主要手法的假設了，但這個案子還有另一個疑點：升級前的映射沒有出錯，升級後就出錯了。&lt;/p&gt;
&lt;p&gt;馬上進入粗暴推理階段：我們知道 AutoMapper 會嘗試去找名稱有關的東西來試試看，而開頭場景中轉型失敗的欄位名稱是 &lt;code&gt;ProductOrder&lt;/code&gt;，也就是說 AutoMapper 會嘗試去抓 &lt;code&gt;Product&lt;/code&gt; 的 &lt;code&gt;Order&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;正好，來源型別的確有一個 &lt;code&gt;IEnumerable&amp;lt;Product&amp;gt; Product&lt;/code&gt; 的成員。但是 &lt;code&gt;Order&lt;/code&gt; 又是哪裡來的呢？為什麼升級前找不到、升級後反而找到了呢？&lt;/p&gt;
&lt;p&gt;噹噹！讓我們看看 .Net 7 Preview 的介紹：&lt;a href=&#34;https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-7/#simplified-ordering-with-system.linq&#34;&gt;Order and OrderDescending&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;System.Linq now has the methods &lt;code&gt;Order&lt;/code&gt; and &lt;code&gt;OrderDescending&lt;/code&gt;, which are there to order an IEnumerable according to T.&lt;/p&gt;
&lt;p&gt;System.Linq 現在具有方法 &lt;code&gt;Order&lt;/code&gt; 與 &lt;code&gt;OrderDescending&lt;/code&gt; ，可用於根據 T 對 IEnumerable 進行排序。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這樣就有了 &lt;code&gt;Order&lt;/code&gt; 這片拼圖，我們的假設也就很明確了：因為升級之後多了 &lt;code&gt;Order()&lt;/code&gt; 這個東東，成功滿足了 Flattening 的條件，原本找不到映射來源會保留預設值的 AutoMapper，在看見了 &lt;code&gt;Order&lt;/code&gt; 之後覺得「這名字好像有關ㄟ，來試試看」，進而發生了 &lt;code&gt;Error mapping types.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;馬上用這組條件測試看看：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Product&amp;gt; Product { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Product&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Target&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ProductOrder { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; source = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Source
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Product = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Product&amp;gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; cfg.CreateMap&amp;lt;Source, Target&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; target = mapper.Map&amp;lt;Target&amp;gt;(source); &lt;span style=&#34;color:#75715e&#34;&gt;// Error mapping types!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target.ProductOrder.Dump(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Error mapping types.

Mapping types:
Source -&amp;gt; Target
UserQuery+Source -&amp;gt; UserQuery+Target

Type Map configuration:
Source -&amp;gt; Target
UserQuery+Source -&amp;gt; UserQuery+Target

Destination Member:
ProductOrder

Missing type map configuration or unsupported mapping.

Mapping types:
OrderedEnumerable`2 -&amp;gt; Int32
System.Linq.OrderedEnumerable`2[[UserQuery+Product...]] -&amp;gt; System.Int32

Destination Member:
ProductOrder
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;重現成功，收工下班。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：隨著 AutoMapper 新版本開始收費，我們也正在緩慢地進行搬遷。&lt;br/&gt;如果你也在搬家，這些文章推薦給你：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.azurewebsites.net/mrkt/2025/04/12/161408&#34;&gt;替換映射工具 - 使用 Mapster | mrkt的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/mrkt/2025/04/13/011927&#34;&gt;另一種映射工具 - Mapperly | mrkt的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;</description>
    </item>
    
    <item>
      <title>從紙筆到電子白板，我的 Heptabase 使用場景</title>
      <link>https://igouist.github.io/post/2026/01/heptabase/</link>
      <pubDate>Sat, 31 Jan 2026 23:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2026/01/heptabase/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;這篇是我的 &lt;a href=&#34;https://blogblog.club/party/&#34;&gt;2026 年 1 月份 BlogBlog 同樂會&lt;/a&gt; 投稿，本月主題是「&lt;a href=&#34;https://wiwi.blog/blog/blogblog-party-jan-2026/&#34;&gt;推坑&lt;/a&gt;」。如果你有自己的部落格，歡迎一起來參加！&lt;/p&gt;
&lt;p&gt;如果你跟我一樣想看其他朋朋的推坑文看到爽，可以到：&lt;a href=&#34;https://wiwi.blog/blog/blogblog-party-recap-jan-2026/&#34;&gt;「推坑」──2026 年 1 月 BlogBlog 同樂會回顧&lt;/a&gt; ，有各式各樣的推坑文讓你三餐配飯、一次滿足。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;本月主題是「推坑」，原本想不太到要推坑什麼，但看到 &lt;a href=&#34;https://www.threads.com/@wu_pingju/post/DSUW8b_gcNU?xmt=AQF0iJjofWEPVn5VlvqWdgF6hUqBWlHI6MtDInVMXsN4HA&#34;&gt;PJ 留言&lt;/a&gt;「問就是推坑 Heptabase」後，心想：「對呀，我每天都在用 Heptabase，不如就推坑一篇吧！」&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;按照慣例，推坑開始之前還是要介紹一下什麼是 &lt;a href=&#34;https://heptabase.com/&#34;&gt;Heptabase&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;這邊直接引用官方 Wiki 和教學影片，基本上看了影片就能馬上知道這軟體是做什麼用的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Heptabase 是一個&lt;strong&gt;專門幫助你學習和研究複雜主題、對事物建立深度理解&lt;/strong&gt;的視覺化筆記軟體。&lt;/p&gt;&lt;/blockquote&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/HgvR2QkfwG0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;br/&gt;
&lt;p&gt;雖然說要寫一篇來推坑，但關於 Heptabase 的介紹和教學已經一抓一大把了。&lt;/p&gt;
&lt;p&gt;例如官方 Wiki 上就有足夠的使用案例和說明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wiki.heptabase.com/how-Heptabases-founder-use-Heptabase-for-learning?lang=zh-Hant&#34;&gt;Founder&amp;rsquo;s Demo | Heptabase Public Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wiki.heptabase.com/use-case-and-workflow?lang=zh-Hant&#34;&gt;Use Case and Workflow | Heptabase Public Wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;或是 Alan 分享的使用流程（當初就是因為這篇文章和裡面那部四小時的示範影片才入坑的）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/heptabase/the-best-way-to-acquire-knowledge-from-readings-abf9357814d1#c3cd&#34;&gt;The Best Way to Acquire Knowledge from Readings | by 詹雨安 Alan Chan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;當然還有更多介紹文，丟幾篇我個人喜歡的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pinchlime.com/heptabase-introduction/&#34;&gt;Heptabase 完整介紹 - 以卡片和白板為基礎，最能讓你進入心流的視覺化學習軟體 - Pin 起來！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://life.huli.tw/2025/02/23/heptabase-review/&#34;&gt;我為什麼用、怎麼用 Heptabase？實際範例與心得 - Huli&amp;rsquo;s blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hyuanverse.com/heptabase-daily-workflow/&#34;&gt;Heptabase 使用場景：日常工作流 - 元宇宙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@chasehuang/heptabase-visual-note-taking-a81d79eff57d&#34;&gt;最有效的學習方式？Heptabase 建立視覺化筆記的 5 種應用 | by Chase Huang&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果看見這篇文章的人，是想學習使用 Heptabase，那麼上面的資訊已經相當充足了。&lt;/p&gt;
&lt;p&gt;那麼，所謂的「推坑文」到底需要什麼呢？果然還是需要推坑者使用的場景和心得吧。&lt;/p&gt;
&lt;p&gt;綜上所述，接下來的篇幅就用來介紹我覺得好用的幾個地方了。以下全是個人經驗和主觀意見，充滿大量五星好評吹捧，想到什麼寫什麼。&lt;/p&gt;
&lt;p&gt;如果看完發現沒什麼幫助，還請看在前面丟了一堆（別人的）資源的份上原諒我，阿彌陀佛。&lt;/p&gt;
&lt;h2 id=&#34;從紙筆到電子白板&#34;&gt;從紙筆到電子白板&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;「喔，你是說你在腦中思考，然後記錄在紙上。」 &lt;br/&gt;
󠀠『不是。這不是紀錄，這只是過程。你必須在紙上思考，這就是那張紙。』&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在開始介紹使用場景之前，要先介紹一下我習慣的筆記方式。從小時候（？）還在學校念書做筆記開始，我就喜歡拿一張白紙，用簡單的文字和箭頭來整理腦子的想法，從第一個字詞開始，逐步地把東西倒出來，邊寫邊嘗試排列出一個方便好記的脈絡。&lt;/p&gt;
&lt;p&gt;當時我發覺：如果只是在腦中想過，還是很容易忘；如果是單純閱讀課本或抄寫定義，那更不可能記得，只有像這種圖像式的關聯，才能稍微把記憶留得久一點，即使忘記了，也能藉由筆記上的圖樣更快地喚醒當初的印象。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1769871600_1.webp&#34;
  alt=&#34;&#34;width=&#34;1477&#34; height=&#34;1108&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 跟同事討論分層架構時的筆記，這已經是少數字跡正常的紙筆紀錄了，還請包涵&lt;/p&gt;
&lt;p&gt;過程之中也經過了幾次嘗試，有時候先把大框架畫出來、有時候先用第一張紙做條列，第二張紙才做整理、有時候就只是先寫上一個關鍵字，然後想到什麼寫什麼，但都還是遵循在紙上寫字畫箭頭的基本方向。現在回頭看，那也許就是我建構思考框架的過程。我喜歡將資料倒到一個平面上，然後像積木般地擺弄它們、畫上箭頭、用方框分群……試著從中找出關聯。&lt;/p&gt;
&lt;p&gt;後來，我認識了 DIKW 模型（具體來說是下面這張圖）才明白：我其實是在學習從資料整理出知識的方法。而且，大多數人也是這樣形成新知識的，只是我的慣用工具是紙和筆。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1769871600_2.webp&#34;
  alt=&#34;&#34;width=&#34;768&#34; height=&#34;250&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ DIKW 模型：從資料到智慧的漸進關係，資料被整理出脈絡成為資訊，資訊被理解並內化成為知識，而將知識應用在判斷與行動上就成為智慧。有興趣的朋友可以參照這篇：&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10263698&#34;&gt;知識是如何形成的？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;隨著摸電腦的時數越來越多，漸漸地也開始放下紙跟筆，踏入了筆記軟體這個大坑。&lt;/p&gt;
&lt;p&gt;然而沒多久就遇到了一個問題：整理太痛苦了。就像那句經典的「寫作之難，在於把網狀的思考，用樹狀的結構，轉換成線性的文字」，當時大多筆記軟體基本上就是一頁文字檔，和我原本的思考方式產生了很大的撕裂感，也導致製作筆記的摩擦力飆升。&lt;/p&gt;
&lt;p&gt;很多時候，我仍然需要先使用外部工具（通常是一張紙）梳理完之後，再降維成一束文字塞到筆記軟體裡。我甚至發現，比起筆記軟體，Whimsical 或 Miro 這種數位白板用起來反而更順手。&lt;/p&gt;
&lt;p&gt;後來又輾轉嘗試了多個筆記軟體，例如 Obsidian（我至今還是很喜歡那個視覺化的關聯圖跟豐富的擴充套件生態）、Notion（資料庫頁面的標籤可以下拉選單很方便，現在還是有用來管理我的書櫃）等等，也嘗試過在筆記內嵌 Drawio 的圖、用擴充套件來加入 Mermaid，但都沒能達到想像中的效果。&lt;/p&gt;
&lt;p&gt;直到有一天，發現了一個主打白板的軟體：Heptabase。&lt;/p&gt;
&lt;p&gt;這就是我尋找已久的筆記方式。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;白板的真實本質是它是一張巨大的、無邊界的桌子。無論你是否自認為是視覺型思考者，每個人都需要一張桌子。桌子的大小決定你能同時看到多少資訊，進而影響你思考能延伸多遠。（&lt;a href=&#34;https://sheracaolity.ghost.io/the-best-way-to-use-ai-for-learning/&#34;&gt;The Best Way to Use AI for Learning - Yu An Chan&lt;/a&gt;）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1769871600_3.webp&#34;
  alt=&#34;&#34;width=&#34;1947&#34; height=&#34;1917&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 搬家後的第一個白板：把物件導向跟依賴注入的筆記整理過去&lt;/p&gt;
&lt;p&gt;我們可以在白板上自由地加上筆記、拉出線條，把筆記分群上色，實際上就這麼簡單。&lt;/p&gt;
&lt;p&gt;但在快樂蜜月期（剛接觸筆記軟體都會有的一段時期，會瘋狂地加入筆記和試用各種功能，特徵是會看著自己的筆記呵呵笑）時，我還發現了更多白板帶來的優點：&lt;/p&gt;
&lt;p&gt;第一項就是能用的空間變大了。原本受限於紙張大小的我，寫個字都很彆扭，不得已只能用抽象攏統的字代替。但現在一個節點是一則筆記，可以自由地完善這則筆記，我能用的空間和深度都大了許多，可以說是舒適無比。&lt;/p&gt;
&lt;p&gt;第二個則是：在這個整合、梳理資料的過程中，我們很容易發現自己的疏漏。兩則筆記似乎應該要有關聯，但感覺線拉得很牽強？中間很可能少了一個關鍵觀念；把筆記分群之後感覺有一區特別稀疏？那就是補強的時候了。這種感覺很接近撰寫部落格文章時那種邊寫邊學的狀態，就是文章寫到一半才發現需要調查更多資訊，最終被迫在過程中學習到更多東西，這也算是一種輸出式學習了吧。&lt;/p&gt;
&lt;p&gt;最後一個則是成就感。畢竟我是一個還算虛榮（？）的人，基本上就是會看著自己的作品沾沾自喜的類型（例如遊戲白金獎盃，或是自己覺得設計得很漂亮的程式架構）。白板本身就要求我們盤點自己的知識點、不斷整合資訊，整理出明確可見的關聯、在過程中擴張知識邊界。不斷重複以上步驟，在一波忘我的忙碌之後，發現白板已經隱隱約約有了一些知識的輪廓，對我這樣的人來說，沒有什麼比這個更令人愉悅了吧。&lt;/p&gt;
&lt;p&gt;諸如此類，如果濃縮成一句心得的話，那就是：白板，真棒呀。（真是詞窮的表現）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：Heptabase 在九月上線了 &lt;a href=&#34;https://heptabase.com/gallery&#34;&gt;Heptabase Gallery&lt;/a&gt;，裡面已經有許多朋朋分享的白板，可以上去逛逛其他人是怎麼使用白板，或是說，是怎麼思考、理解、梳理某個問題的。我一直對別人怎麼做這些事很有興趣，畫廊跟白板的 Publish 功能給了一個很棒的場景。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;資料來源的好幫手readwise-與它的家族&#34;&gt;資料來源的好幫手：Readwise 與它的家族&lt;/h2&gt;
&lt;p&gt;在前面一頓對白板的無情吹捧之後，我們還是要面對現實：&lt;br/&gt;如果我們要把知識點放到白板，我們必須要先有知識點（對，我知道有點廢話）&lt;/p&gt;
&lt;p&gt;也就是說，我們需要先確認兩件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;來源有哪些？我們去哪裡閱讀新的資訊？&lt;/li&gt;
&lt;li&gt;我們要如何把這些資訊丟到我們的筆記軟體？&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;
&lt;p&gt;我目前閱讀的管道主要來自三個：RSS 訂閱、KOBO 電子書，以及社群媒體&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RSS 用來訂閱我感興趣的網站&lt;/li&gt;
&lt;li&gt;KOBO 是通勤路上的好夥伴&lt;/li&gt;
&lt;li&gt;社群媒體用來打發碎片化時間&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;友善附註：RSS，是一種自古以來就存在（？）的訂閱方式。主要是將網站裡文章的標題和簡介等資訊整理成 XML 的文字格式（例如本站的 RSS 頁面），使訂閱服務只需要去各個網站抓取輕便的文字檔就能夠得知網站是否更新、現在有哪些文章等資訊，非常方便。&lt;/p&gt;
&lt;p&gt;更重要的是，藉由自己選擇要訂閱的網站，就能讓我們自己決定資訊來源，在噪音多到爆炸的網路上可說是一片淨土。有興趣的朋友可以閱讀：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://migrantdiaries.bearblog.dev/design-your-own-newspaper/&#34;&gt;RSS：一份由你設計的報紙 | Tian-Yan&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wiwi.blog/blog/you-should-use-rss/&#34;&gt;你需要用 RSS，不要再拖了 | Wiwi.Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;當我們提到「如何把這些內容丟到 Heptabase」，就必須提另一個常一起玩的好夥伴：&lt;a href=&#34;https://readwise.io/&#34;&gt;Readwise&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Readwise 是用來玩資料串串樂的，我們可以從 Kobo、Twitter、Medium 等軟體把閱讀時的劃線重點 Import 到 Readwise，再從 Readwise 把這些劃線重點 Export 到 Heptabase、Notion、Obsidian，基本上就跟《瘟疫公司》裡的鳥禽類作用一樣：從Ａ國家帶著病毒飛越大海散佈給Ｂ國家，帶來知識與希望。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;題外話：我常覺得 Readwise 的 Twitter 同步做法很有趣，你需要把想收藏的推文用訊息發給 &lt;a href=&#34;https://x.com/readwise&#34;&gt;@readwise&lt;/a&gt; 這個帳號，它就會同步到你的 Readwise 上。每次按「透過聊天發送」都有一種「欸你先幫我把這東西拿回家」的錯覺&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;常被同時提到的還有 &lt;a href=&#34;https://readwise.io/read&#34;&gt;Readwise Reader&lt;/a&gt;，顧名思義是個閱讀工具，用來管理並閱讀 RSS 訂閱內容。並且在 Readwise Reader 劃的重點也會自動同步到 Readwise，進而同步到我們設定好的 Export 軟體，例如 Heptabase，直接做到一條龍。&lt;/p&gt;
&lt;p&gt;我原本用來管理 RSS 訂閱的是 Feedly（還寫了一篇 &lt;a href=&#34;https://igouist.github.io/post/2020/04/feedly/&#34;&gt;Feedly —— 用 RSS 訂閱來主動篩選資訊吧&lt;/a&gt;），但在接觸 Readwise Reader 的強大整合就毅然決然跳槽了。&lt;/p&gt;
&lt;p&gt;除了 RSS 訂閱內容作為出發點的這條路線以外，Readwise 也提供了瀏覽器擴充套件 &lt;a href=&#34;https://chromewebstore.google.com/detail/readwise-highlighter/jjhefcfhmnkfeepcpnilbbkaadhngkbi?hl=zh-TW&amp;amp;itemlang=it&#34;&gt;Readwise Highlighter&lt;/a&gt;，可以直接在網頁上劃線，並將網頁內容同步到 Readwise Reader。如果閱讀到不錯的網頁，例如偶然發現的超讚部落格文章，就可以直接用 Highlighter 劃線並收藏。&lt;/p&gt;
&lt;p&gt;我們從 Readwise Highlighter 和 Readwise Reader 收藏的劃線，最終會從 Readwise 同步到 Heptabase，並集中在 Heptabase 的 Highlight 頁面，後續就可以在我們的白板上取用，也能在每則 Highlight 底下繼續加上筆記、關聯其他筆記。&lt;/p&gt;
&lt;p&gt;最重要的是：我們能在 Heptabase 用搜尋找到 Highlight，這個搜尋功能多次幫助了腦子只剩下一堆碎片的我成功找回當初看到的某篇文章，幫助很大，必須額外花一句話來稱讚。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1769871600_4.webp&#34;
  alt=&#34;&#34;width=&#34;3840&#34; height=&#34;2038&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ Highlight 頁面，可以看見從各個地方同步來的劃線筆記&lt;/p&gt;
&lt;p&gt;聊完 Readwise 之後，另一個把資料丟進 Heptabase 的常用工具是官方的瀏覽器擴充套件 &lt;a href=&#34;https://chromewebstore.google.com/detail/heptabase-web-clipper/mlnkfjffdpccejmkcdhhbjfmdfokmkjh&#34;&gt;Heptabase Web Clipper&lt;/a&gt;，它會將網頁全文摘取到 Heptabase 中並建立一張卡片，還會標上來源連結。但由於我的外部資料主要集中在 Highlight 了，並且很難區分 Web Clipper 建立的卡片和我自己建立的卡片，對很喜歡在文章中加入一大堆外部連結的我來說有點困擾，因此使用的次數相對較少，只有在我打算留存全文、綁架人家的文章回家拆成卡片的時候才會使用。&lt;/p&gt;
&lt;p&gt;現在我們已經簡短認識了 Readwise 家族，以及 Heptabase 自己推出的 Web，是跟上面的資訊來源（RSS、KOBO、社群）進行連連看的時候了。目前我的蒐集管道如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RSS 訂閱 → Readwise Reader（劃線）→ Readwise → Heptabase&lt;/li&gt;
&lt;li&gt;文章閱讀 → Readwise Reader（劃線）→ Readwise → Heptabase&lt;/li&gt;
&lt;li&gt;文章閱讀（如果想存全文） → Heptabase Web Clipper → Heptabase&lt;/li&gt;
&lt;li&gt;KOBO → Readwise → Heptabase&lt;/li&gt;
&lt;li&gt;Twitter → Readwise → Heptabase&lt;/li&gt;
&lt;li&gt;Facebook → 分享連結到 Readwise Reader → Readwise → Heptabase&lt;/li&gt;
&lt;li&gt;電子報 → 用 Readwise Reader 信箱訂閱 → Readwise → Heptabase&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;藉由這些路線，就可以把各處蒐集來的資訊集中到 Heptabase，成為後續筆記的養份。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;延伸閱讀，順便丟一些 Readwise 介紹文章給有興趣的朋友：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://readingoutpost.com/readwise-reader-3-year-review/&#34;&gt;Readwise Reader 付費訂閱三年評價，AI 快時代的「慢閱讀」&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gomrcuriosity.com/readwise-reader-adding-contents/&#34;&gt;全方位閱讀軟體 Readwise Reader ── 文章匯入篇 - 好奇先生&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hyuanverse.com/readwise-heptabase-use-case/&#34;&gt;Readwise + Heptabase 使用案例分享：讀文章、做筆記原來那麼簡單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@Felix.helps.you/%E9%97%9C%E6%96%BC%E9%96%B1%E8%AE%80%E8%88%87%E5%AD%B8%E7%BF%92%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%B5%81-readwise-reader-obsidian-bdf01716e369&#34;&gt;關於閱讀與學習的工作流：Readwise Reader + Obsidian - 賴澤霖 - Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;我所在意的筆記軟體功能雙向鏈結日誌搜尋&#34;&gt;我所在意的筆記軟體功能：雙向鏈結、日誌、搜尋&lt;/h2&gt;
&lt;p&gt;前面聊完白板跟資料來源這兩個我最在意的重點，剩下的篇幅就用來聊聊一些我喜歡的功能吧。&lt;/p&gt;
&lt;p&gt;首先一定要提的就是雙向鏈結，我最早是在 Roam Research 接觸到雙鏈這個概念的（現在似乎已經成為大多筆記的標配？），實際用了一段時間之後的確再也離不開了，因此第一段功能介紹就留給雙鏈。&lt;/p&gt;
&lt;p&gt;雙向鏈結指的是「我在Ａ筆記提到Ｂ筆記，那麼我在Ｂ筆記也能看到Ａ筆記提到了Ｂ筆記」，其中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「Ａ筆記提到Ｂ筆記」是我們常見的正向鏈結&lt;/li&gt;
&lt;li&gt;「Ｂ筆記也能看到Ａ筆記提到了Ｂ筆記」就是反向鏈結（Backlinks）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;……用說的有點饒口，其實就是以前無名小站的「誰來我家」功能，只是對象換成了筆記，哪則筆記來我家。&lt;/p&gt;
&lt;p&gt;利用反向鏈結，就可以從Ｂ筆記發現Ａ筆記的內容。尤其像我這種寫個文章都要插一堆超連結的人，可能撰寫Ａ筆記的時候，剛好覺得這和Ｂ筆記有關聯，就會順手標記上去。過了很久很久以後，當我瀏覽到Ｂ筆記，就能馬上掌握Ｂ和Ａ的關聯，這時候都會有一種賺到了的感覺。&lt;/p&gt;
&lt;p&gt;如果以上的ＡＢ優酪乳大亂鬥看得有點亂，讓我們來看幾個簡單的例子：&lt;/p&gt;
&lt;p&gt;以我在整理物件導向相關的筆記為例：我在單一職責原則、依賴反轉原則…等筆記都會提到跟介面的關係。那麼，我下次到介面的筆記時，就可以藉由反向連結，直接看到各個原則是怎麼應用介面的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1769871600_5.webp&#34;
  alt=&#34;&#34;width=&#34;1304&#34; height=&#34;1332&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了發現筆記間的關聯，並且能以某則筆記為主角看看其他筆記提到的內容以外，在日常工作上其實也有妙處。&lt;/p&gt;
&lt;p&gt;我習慣在每天先到 Journal 頁面（類似每日筆記的地方）列好當天的任務，有進度的時候也會在 Journal 頁面關聯任務筆記，並放上相關的討論和紀錄（通常是隨手打個一兩句）&lt;/p&gt;
&lt;p&gt;如此一來，當我有需要（？）的時候，就能在任務卡片的反向鏈結迅速確認每天的任務狀況和相關紀錄，還原專案的歷程及脈絡，意外地相當方便。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1769871600_6.webp&#34;
  alt=&#34;&#34;width=&#34;1328&#34; height=&#34;1072&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;既然前面提到了 Journal，第二個就來聊聊我很需要的部份：日誌&lt;/p&gt;
&lt;p&gt;我並不是喜歡在筆記軟體中頻繁切換畫面的人，那很容易打斷心流。因此，我更偏好大部份的工作都在少數幾個頁面中完成，像是白板能夠在畫布上完成知識點的拆分和梳理，就完全滿足這個需求。&lt;/p&gt;
&lt;p&gt;但許多日常工作和臨時想法是無法當場花時間進行組織和整理的，我從 Roam Research 到 Obsidian 都已經習慣「開一個 Daily Note 來當作每日快速筆記」的做法。幸好，Heptabase 的每日筆記（Journal）非常好用，搭配側欄也不容易打斷心流，基本上成為了我日常工作的重心。&lt;/p&gt;
&lt;p&gt;因此，我在使用 Heptabase 的時候，核心頁面就是這兩個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;當我正在研究一個主題、整理一組筆記，我的主要頁面是白板&lt;/li&gt;
&lt;li&gt;當我每天執行工作、確認待辦項目、快速蒐集想法時，我的主要頁面是日誌&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以今天（對，身為一個拖延症重度患者，這篇文章是壓線到最後一天寫的）的日誌頁面為例：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1769871600_7.webp&#34;
  alt=&#34;&#34;width=&#34;3840&#34; height=&#34;2242&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見我習慣把日誌頁面分成三個部分：本日行程、待辦項目、每日紀錄。&lt;/p&gt;
&lt;p&gt;每天我打開 Heptabase，就是先到日誌整理好當天行程、決定待辦項目、把相關的紀錄放到底下（像是某某專案決定怎麼實作、發現一個適合寫作的好點子、跟朋友聊到一個值得紀錄的概念，或只是想到什麼寫什麼）。每則紀錄都關聯到對應的筆記條目，後續就可以像前面雙向鏈結提到的，在各個筆記頁面用反向鏈結確認每天留下的紀錄。&lt;/p&gt;
&lt;p&gt;同樣地，也可以使用關聯的方式，確保某天會需要的資訊自動出現在某天的日誌：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;假設約好了下禮拜四要跟家人出門吃飯，就可以直接關聯下週四的日誌並打上「吃飯」，等到下週四的時候這則「吃飯」就會出現在日誌頁面底下的反向鏈結&lt;/li&gt;
&lt;li&gt;假設某個專案預定在禮拜一完成Ａ任務、禮拜二完成Ｂ任務，我習慣將專案預計需要進行的步驟先全部列好，這時候就可以直接在專案筆記將Ａ任務的日期標為禮拜一，在禮拜一的日誌底下反向鏈結就會提到這個任務&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：在上面的例子，如果是用待辦項目（Todo list）標記了某個日期的話，該日期的 Journal 右上角 todos 也會出現這則待辦（這真的是我最喜歡的更新之一，對我這種到處放待辦事項的人來說根本是德政）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;搭配側邊攔直接開始相關的筆記頁面（例如整理專案資訊的筆記，又或者今天會用到的知識點筆記），可以做到在日誌畫面就處理完大部份的工作，說不定我停留在日誌頁面的時間還比白板長也不一定呢。&lt;/p&gt;
&lt;p&gt;藉由白板跟雙向鏈結，就能盡可能保留筆記之間的關聯和脈絡，做出網狀般的知識庫。而搭配日誌、同步外部資料、擷取網頁，就能讓外部的資料有一個入口，提供知識庫源源不絕的燃料。當我打算列出一些我會在意的功能時，首先想到的就是這些，這可能也代表了這些功能對我的不可或缺。&lt;/p&gt;
&lt;p&gt;當然，我還有許多在意的地方，例如搜尋。因為我記性不是很好，除了已經動手到白板的筆記以外，有些零散的筆記、隨手畫的線、從優質文章摘取下來的片段，平常都是散落在我的卡片庫裡的，頂多標了個標籤作為提醒。因此，我在使用筆記軟體時，其實是十分依賴搜尋功能的。Heptabase 的搜尋目前體感還蠻快的，而且可以同時搜尋到筆記內容和劃線重點，已滿足我的需求。&lt;/p&gt;
&lt;p&gt;又或者看板，因為我的筆記軟體大多同時承載知識管理和專案管理的工作，對我來說，工作上的專案勢必會應用到先前累積的知識，兩者是無法分離的。而在 Heptabase 中，可以藉由標籤來將同性質的筆記列在一組標籤資料庫（Tag Database）裡，再加上一組專案進度的屬性，就能用看板（Kanban）的方式進行管理，這點也相當足夠。&lt;/p&gt;
&lt;p&gt;諸如此類的部份就不展開 細講了。&lt;del&gt;主要是因為寫到這邊已經快晚上十點，感覺要來不及寫完了&lt;/del&gt;&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;從大學寫筆記開始，一路用了 Evernote、Roam Research、Obsidian，最後半定居在 Hetpabase。最主要的因素還是前面提到的幾個場景（白板、雙鏈、日誌）都有符合我的需求。尤其是白板。既契合了我學習和思考的模式，又能保留更多的脈絡。&lt;/p&gt;
&lt;p&gt;當我們看見一幅白板，甚至能大略知道這個人是怎麼理解這個主題的，又是怎麼拆解、怎麼組裝這些知識的（當然，這個「人」也包括六個月前的自己），白板保留了一部份思路，進而讓我們能窺見這個過程。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;要打造一個脈絡化的知識網路，第二件事情是要確保所有知識和想法背後的思考脈絡都能被完整保存和追蹤。當你看到一個想法時，你必須能回憶起這個想法是怎麼產生的、又在哪些場景下被使用。&lt;/p&gt;
&lt;p&gt;人類創造出的所有知識和想法，都是先有輸入，才有輸出。「追蹤思考脈絡」其實就是在幫助我們暸解什麼樣的輸入造成了什麼樣的輸出。也因此，我們必需打通生命週期中的「收集」、「思考」、「創作」這三個環節。（&lt;a href=&#34;https://wiki.heptabase.com/the-knowledge-lifecycle?lang=zh-Hant&#34;&gt;04 - The Knowledge Lifecycle&lt;/a&gt;）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;甚至在看別人分享他們如何使用 Heptabase 整理筆記、自己也實際使用 Heptabase 整理筆記之後，會有種感覺：也許我們真的能藉由這種方式來學習如何掌握知識，最終讓自己變得更好。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A good thinking tool shouldn&amp;rsquo;t just hand users answers to their questions, but also guide and &lt;strong&gt;enable them to discover and articulate more complex questions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;一個好的思考工具不應該只是給用戶提供問題的答案，還應該引導並使他們能夠發現和表達更複雜的問題。（&lt;a href=&#34;https://stream.thesephist.com/updates/1726954880&#34;&gt;Linux’s stream&lt;/a&gt;）&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;決定 Heptabase 做為推坑主題之後，我一直在想：所謂的「推坑」到底是什麼呢？&lt;/p&gt;
&lt;p&gt;尤其開始動手寫之前，我的想像一直被侷限在「推銷」，也就是「哇！這個東西有多麼多麼好」，或是是「你為什麼該用這個東西」。但是越寫越覺得，這好像不是我想要表達的方向。&lt;/p&gt;
&lt;p&gt;看了幾篇推坑文，又回頭想想才發現：我是一個看到別人吃好吃的東西就會想吃、看到別人玩好玩的遊戲就會想玩的人。所以，能夠推坑我的東西，其實都有一個共通的特點：他們自己用得很開心。&lt;/p&gt;
&lt;p&gt;只有對方分享自己的快樂，才會讓我想要嘗試。我甚至會想：也許最尊重對方的推坑方式，就是過好自己的生活也不一定。&lt;/p&gt;
&lt;p&gt;所以，我最後決定用這樣的方式呈現：用我個人的使用場景來記錄這篇推坑。&lt;/p&gt;
&lt;p&gt;如果你讀到這裡，然後你使用筆記的場景，跟我也有那麼一點類似，那麼我會推薦你：試試看 Heptabase 吧！&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>記一次把部落格圖片從 Imgur 搬到 Cloudflare R2 的過程</title>
      <link>https://igouist.github.io/post/2025/07/migrate-blog-images-to-cloudflare-r2/</link>
      <pubDate>Thu, 31 Jul 2025 23:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/07/migrate-blog-images-to-cloudflare-r2/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753973577696.webp&#34;
  alt=&#34;1753973862036&#34;width=&#34;600&#34; height=&#34;360&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在上個月的 &lt;a href=&#34;https://igouist.github.io/post/2025/06/imgur-temporarily-over-capacity-maybe-your-ip-banned/&#34;&gt;Imgur 一直 temporarily over capacity 嗎？先檢查網路看看吧&lt;/a&gt; 提到，因為 Imgur 不給上傳圖片了，現在寫個文章還得開 VPN 才能貼圖片。&lt;/p&gt;
&lt;p&gt;拖著拖著七月也要過了，決定趁休假的時候來把圖床搬一搬。&lt;/p&gt;
&lt;p&gt;比起實作上的拖延，搬家目標則早早決定好要嘗試這篇「&lt;a href=&#34;https://blog.kyomind.tw/weekly-review-43/&#34;&gt;Imgur 封鎖台灣 IP，我把圖床搬到 Cloudflare R2 - Code and Me&lt;/a&gt;」提到的 &lt;a href=&#34;https://www.cloudflare.com/zh-tw/developer-platform/products/r2/&#34;&gt;Cloudflare R2&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;畢竟&lt;strong&gt;出口流量免費&lt;/strong&gt;實在太香了，不愧是賽博菩薩。這就馬上前來皈依。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;考慮到每位朋朋的部落格選型不同，先說明一下我家的狀況：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 Hugo 建置，直接丟在 Github Pages&lt;/li&gt;
&lt;li&gt;圖片目前放在 Imgur&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這代表有些較為暴力的操作可能只適用於我目前的狀況，例如直接在檔案裡搜尋 &lt;code&gt;imgur&lt;/code&gt; 來抓圖等等（所以如果你是從關鍵字搜尋進來這篇文章的，請從右手邊的文章目錄直接前往你需要的小節）&lt;/p&gt;
&lt;p&gt;這次圖床搬家主要進行了以下步驟：&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#建立-cloudflare-r2-資源&#34;&gt;建立 Cloudflare R2 資源&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#弄個-powershell-腳本把部落格的圖片先抓下來&#34;&gt;弄個 Powershell 腳本把部落格的圖片先抓下來&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#插件順便用-webp-把圖片做一下壓縮&#34;&gt;插件：順便用 WebP 把圖片做一下壓縮&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#上傳圖片到-cloudflare-r2&#34;&gt;上傳圖片到 Cloudflare R2&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-vscode-的-markdown-image-延伸模組直接上傳圖片到-cloudflare-r2&#34;&gt;使用 VSCode 的 Markdown-image 延伸模組直接上傳圖片到 Cloudflare R2&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;p&gt;如此如此，說搬就搬，這篇就紀錄一下這次的圖床搬家過程。&lt;/p&gt;
&lt;h2 id=&#34;建立-cloudflare-r2-資源&#34;&gt;建立 Cloudflare R2 資源&lt;/h2&gt;
&lt;p&gt;既然都決定要搬家到 Cloudflare R2 了，首先當然是要建立一個 Cloudflare R2 資源。&lt;/p&gt;
&lt;p&gt;建立 R2 的操作可以參考這篇「&lt;a href=&#34;https://ivonblog.com/posts/cloudflare-r2-image-hosting/&#34;&gt;架設 Cloudflare R2 免費圖床，給 Hugo 靜態網站託管圖片 - Ivon 的部落格&lt;/a&gt;」的「新增 Cloudflare R2 bucket」小節，流程詳細還附圖，基本可以照著把 R2 儲存空間給建出來。&lt;/p&gt;
&lt;p&gt;總之先到 &lt;a href=&#34;https://dash.cloudflare.com/&#34;&gt;Cloudflare 儀表板&lt;/a&gt;，把 R2 儲存體建出來，然後順手掛個自訂網域：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970440386.webp&#34;
  alt=&#34;1753970440386&#34;width=&#34;1746&#34; height=&#34;1466&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970454069.webp&#34;
  alt=&#34;1753970454758&#34;width=&#34;2006&#34; height=&#34;502&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：如果想要自訂 R2 資源對外的網址，需要先讓 R2 和 Cloudflare 上存在的網域做綁定，也就是要在 Cloudflare 買網域或是其他地方買來掛在 Cloudflare 上（參考 &lt;a href=&#34;https://developers.cloudflare.com/r2/buckets/public-buckets/#add-your-domain-to-cloudflare&#34;&gt;Public buckets&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;因為我（之前為了&lt;a href=&#34;https://tedliou.com/software/bluesky-handle-domain-customize/&#34;&gt;改 Bluesky 的帳號名稱&lt;/a&gt;）已經在 Cloudflare 購買過網域了，所以可以直接用自定義網域來把圖片網址改成我要的網址。&lt;/p&gt;
&lt;p&gt;如果你也想要自訂圖片網址，但在 Cloudflare 還沒有任何網域，可以先多找幾篇網域購買和託管到 Cloudflare 的文章（例如 &lt;a href=&#34;https://geekaz.net/cloudflare-domain-tutorial/&#34;&gt;Cloudflare 網域購買教學&lt;/a&gt;）參考參考，再決定要不要投入 $$ 買一個自己喜歡的名字。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;建立完畢之後，可以先進貯體隨便上傳一張圖片，確認一下 URL 有沒有連到圖片：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970484783.webp&#34;
  alt=&#34;1753970485658&#34;width=&#34;1526&#34; height=&#34;1703&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;弄個-powershell-腳本把部落格的圖片先抓下來&#34;&gt;弄個 Powershell 腳本把部落格的圖片先抓下來&lt;/h2&gt;
&lt;p&gt;建好 R2 空間之後，就要開始著手搬遷啦～&lt;/p&gt;
&lt;p&gt;我的部落格採用 Hugo + Github Pages，所以文章內容也就是一堆 Markdown 檔案而已。這種狀況下，最快的方式就是直接搜出文章中的圖片連結就行。&lt;/p&gt;
&lt;p&gt;幸好 2025 年的懶人是有福的，直接請 GPT 幫我產一組 Powershell 腳本，把 &lt;code&gt;.md&lt;/code&gt; 檔拖出來，掃到圖片就抓下來存著，迅速搞定這一階段：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# see: https://gist.github.com/mufidu/f7b795f844f1ee4dc78e55123d5a398b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 1. 指定資料夾&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$folder    &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C:\Blog\content&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$outputDir &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C:\Users\xxx\Pictures\Blog-Image-Backup&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 2. 建立目標資料夾（若不存在）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;-not &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Test-Path $outputDir&lt;span style=&#34;color:#f92672&#34;&gt;))&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    New-Item -Path $outputDir -ItemType Directory | Out-Null
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 3. 逐一掃描 .md 檔案&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Get-ChildItem -Path $folder -Filter *.md -Recurse | ForEach-Object &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $mdFile  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $_.FullName
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $content &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Get-Content -Path $mdFile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    foreach &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$line in $content&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 4. 正則擷取 Markdown 圖片 URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$line -match &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;!\[[^\]]*\]\((?&amp;lt;url&amp;gt;https?://[^\)]+)\)&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $url      &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $Matches&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# 5. 以 URL 最後一節當作檔名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $fileName &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Split-Path -Path $url -Leaf
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $destPath &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Join-Path $outputDir $fileName
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            try &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;# 6. 下載圖片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Invoke-WebRequest -Uri $url -OutFile $destPath -UseBasicParsing
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Write-Host &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;已下載： &lt;/span&gt;$url&lt;span style=&#34;color:#e6db74&#34;&gt; → &lt;/span&gt;$destPath&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            catch &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Write-Warning &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;下載失敗： &lt;/span&gt;$url&lt;span style=&#34;color:#e6db74&#34;&gt; (`&lt;/span&gt;$_&lt;span style=&#34;color:#e6db74&#34;&gt;`)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;插件順便用-webp-把圖片做一下壓縮&#34;&gt;插件：順便用 WebP 把圖片做一下壓縮&lt;/h2&gt;
&lt;p&gt;圖片下載完後，突然想起之前跑 &lt;a href=&#34;https://pagespeed.web.dev/?hl=zh-tw&#34;&gt;PageSpeed Insights&lt;/a&gt; 時有被提示過圖檔太大的問題，決定趁這機會順便把圖壓成 WebP。&lt;/p&gt;
&lt;p&gt;WebP 是 Google 推的圖片格式，從 &lt;a href=&#34;https://developers.google.com/speed/webp?hl=zh-tw&#34;&gt;developers.google 對 WebP 的介紹&lt;/a&gt; 來看，WebP 無損壓縮後的檔案大小能比 PNG 小 26%，而有損壓縮在同樣壓縮品下也能比 JPEG 小 25-34%。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：有些情況下跑完 WebP 反而會讓檔案大小變大，例如 JPG 轉 WebP（參見 &lt;a href=&#34;https://stackoverflow.com/questions/30641057/image-size-is-increased-when-converted-from-jpg-to-webp-with-quality-value-100&#34;&gt;Image size is increased when converted from jpg to webp with quality value 100&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;而在 &lt;a href=&#34;https://developers.google.com/speed/webp/faq#can_a_webp_image_grow_larger_than_its_source_image&#34;&gt;WebP 常見問題文檔&lt;/a&gt;的「Can a WebP image grow larger than its source image?」這一小節有說明可能會導致檔案變大的場景，有遇到的朋友可以先確認看看。&lt;/p&gt;
&lt;p&gt;因為我的部落格大多是 PNG 圖檔，文檔提到「Note that converting a JPEG source to lossy WebP, or a PNG source to lossless WebP are not prone to such file size surprises.」所以我仍然可以無腦轉 WebP，如果是跟我一樣都是 PNG 的朋友就別擔心了。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在開始動手之前，需要先確保環境中有 WebP 的工具。沒有的朋朋們請先到 &lt;a href=&#34;https://developers.google.com/speed/webp/download?hl=zh-tw&#34;&gt;Google for Developers&lt;/a&gt; 下載回來（為了方便後續使用，可以把 &lt;code&gt;bin&lt;/code&gt; 的路徑丟進環境變數）&lt;/p&gt;
&lt;p&gt;接著同樣產個小腳本來逐個把圖片壓成 WebP，這段除了好朋友 GPT 以外，還參考了這兩篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.markkulab.net/2021/08/06/powershell-webp-conveter/&#34;&gt;透過 PowerShell 製作 JPG/ PNG 轉 webp 小工具 (使用 Google webp converter lib) | Mark Ku&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@wendyhsinyun/%E7%94%A8%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%A3%93%E7%B8%AE%E5%A4%9A%E5%BC%B5-webp-%E7%85%A7%E7%89%87-8da81540f93e&#34;&gt;用命令行一次壓縮多張 WebP 照片 - HY - Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 記得先下載 WebP 工具: https://developers.google.com/speed/webp/download&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 1. 設定來源與輸出資料夾&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$inputFolder  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C:\Users\xxx\Pictures\Blog-Image-Backup&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$outputFolder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Join-Path $inputFolder &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;WebP&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 2. 建立輸出資料夾（若不存在）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;-not &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Test-Path $outputFolder&lt;span style=&#34;color:#f92672&#34;&gt;))&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    New-Item -Path $outputFolder -ItemType Directory | Out-Null
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 3. 定義可轉檔副檔名清單&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$convertExts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.jpg&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.jpeg&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.png&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 4. 遞迴取得所有檔案，並排除輸出資料夾本身&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Get-ChildItem -Path $inputFolder -File -Recurse |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Where-Object &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; $_.FullName -notlike &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$outputFolder&lt;span style=&#34;color:#e6db74&#34;&gt;*&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ForEach-Object &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $src &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $_.FullName
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $ext &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $_.Extension.ToLower&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$convertExts -contains $ext&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# 5a. 轉檔：jpg/jpeg/png → webp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $dest &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Join-Path $outputFolder &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$_.BaseName + &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.webp&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# 使用 -q 指定壓縮品質（0-100, ex: -q 80）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# 如果需要無損壓縮，可以使用 -lossless 參數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# 但注意有損的 JPG 之類轉無損 WebP 反而會讓檔案變大哦&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;amp; cwebp -lossless $src -o $dest &amp;gt; $null
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Write-Host &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Converted: &lt;/span&gt;$src&lt;span style=&#34;color:#e6db74&#34;&gt; → &lt;/span&gt;$dest&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# 5b. 其餘格式（例如 gif）直接複製&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# 畢竟後續還是都要上傳到新的圖床，不能轉的就直接過去吧&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            $dest &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Join-Path $outputFolder $_.Name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Copy-Item -Path $src -Destination $dest -Force
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Write-Host &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Copied:    &lt;/span&gt;$src&lt;span style=&#34;color:#e6db74&#34;&gt; → &lt;/span&gt;$dest&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;上傳圖片到-cloudflare-r2&#34;&gt;上傳圖片到 Cloudflare R2&lt;/h2&gt;
&lt;p&gt;現在我們有一卡車圖檔準備要大舉進攻 Cloudflare R2，按照上面的節奏。聰明的朋朋也許會想：&lt;br/&gt;這傢伙又要叫 GPT 幫我們產腳本來上傳了吧？&lt;/p&gt;
&lt;p&gt;但很可惜，我請 GPT 產腳本是因為我很懶，所以有更懶的方法存在時，我是連腳本都不考慮的。&lt;/p&gt;
&lt;p&gt;這個步驟最懶的方法就是直接滑鼠點一點，畢竟 Cloudflare R2 在瀏覽器就可以拖檔案上傳了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970509149.webp&#34;
  alt=&#34;1753970510004&#34;width=&#34;2980&#34; height=&#34;1329&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;雖然瀏覽器上傳有每次一百個檔案的限制，但考慮到我部落格 &lt;del&gt;拖稿嚴重&lt;/del&gt; 圖片不多，因此手動上傳也是分分鐘的事情，這個步驟就手動傳一傳收工。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果檔案太多，或是好想用下指令的方式上傳呢？請參考 R2 文檔的 &lt;a href=&#34;https://developers.cloudflare.com/r2/objects/upload-objects/&#34;&gt;Upload objects&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;因為前面的步驟只是下載檔案又上傳檔案，連檔名都沒更動，接著只要回去文章內文把原本有 &lt;code&gt;i.imgur.com&lt;/code&gt; 的連結都替代成新的圖片網址就搞定囉～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果像我一樣，有些 JS 的操作會去呼叫圖片網址的話，可能會看到 CORS 的錯誤訊息，例如：&lt;code&gt;Access to image at &#39;https://xxxx/xxxxxx.webp&#39; from origin &#39;https://xxxxxx.github.io&#39; has been blocked by CORS policy: No &#39;Access-Control-Allow-Origin&#39; header is present on the requested resource.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;這時候只要去 R2 儲存體的「設定」 找到 CORS 原則，把站台加上去就可以了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970523569.webp&#34;
  alt=&#34;1753970524223&#34;width=&#34;2200&#34; height=&#34;577&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;使用-vscode-的-markdown-image-延伸模組直接上傳圖片到-cloudflare-r2&#34;&gt;使用 VSCode 的 Markdown-image 延伸模組直接上傳圖片到 Cloudflare R2&lt;/h2&gt;
&lt;p&gt;到這邊其實部落格的圖床遷移已經完成了，但因為我習慣用 VSCode 撰寫文章，之前都用 vscode-imgur 這款延伸模組，實現貼上圖片自動上傳 Imgur 的舒適體驗。&lt;/p&gt;
&lt;p&gt;但現在圖床搬家到 Cloudflare R2 了，當然也要調整一下 VSCode 的上傳圖片工具。果斷採用開頭這篇「&lt;a href=&#34;https://blog.kyomind.tw/weekly-review-43/&#34;&gt;Imgur 封鎖台灣 IP，我把圖床搬到 Cloudflare R2&lt;/a&gt;」提到的 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image&#34;&gt;Markdown-image&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;另外在設定的過程中還參考了以下兩篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://cunoe.com/blog/markdown-image-extension&#34;&gt;使用 Markdown Image 扩展实现上传图片到 Cloudflare R2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.hao.kim/archives/hugo-vscode-auto-upload-file-to-oss-r2&#34;&gt;Hugo vscode 自动上传图片到cloudflare | Hao DevSecOps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;首先我們得申請一組權杖，可以參考 Cloudflare 官方文檔的 &lt;a href=&#34;https://developers.cloudflare.com/r2/api/tokens/&#34;&gt;Authentication&lt;/a&gt; 操作，或是從 R2 頁面的右側進入 API 權杖的頁面：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970536536.webp&#34;
  alt=&#34;1753970536936&#34;width=&#34;2623&#34; height=&#34;822&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;新增一組能夠「物件讀取和寫入」的權杖後，會來到金鑰畫面。注意這組金鑰跟識別碼只會出現一次，請務必記下來，等等設定延伸模組的時候會用到：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970546278.webp&#34;
  alt=&#34;1753970546886&#34;width=&#34;1561&#34; height=&#34;1421&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;建立完之後，進到我們要上傳圖片的貯體，進到設定頁面，這邊的名稱跟 S3 API 資訊等等也會用到：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970557979.webp&#34;
  alt=&#34;1753970560756&#34;width=&#34;2203&#34; height=&#34;826&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們回到 VSCode，到設定裡面找到 Markdown Image 的相關設定，把上傳圖片的方式改為「S3」（不是 Cloudflare 哦！）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970570066.webp&#34;
  alt=&#34;1753970570818&#34;width=&#34;753&#34; height=&#34;709&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最後就是把剛剛拿到的資訊一個一個填進去設定裡啦～&lt;/p&gt;
&lt;p&gt;這邊提供我填的內容給需要的朋朋參考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;S3: Endpoint =&amp;gt; R2 設定頁面的 S3 API
&lt;ul&gt;
&lt;li&gt;ps: 可以不用放最後面的儲存體名稱，反正等等 Bucket Name 要放&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;S3: Region =&amp;gt; 直接填 auto 就好&lt;/li&gt;
&lt;li&gt;S3: Bucket Name =&amp;gt; 儲存體（貯體）名稱，抄 R2 設定頁面的名稱就好
&lt;ul&gt;
&lt;li&gt;ps: 如果 Endpoint 有包含儲存體名稱了，這邊會開成資料夾&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;S3: Access Key ID =&amp;gt; 發金鑰時拿到的 存取金鑰識別碼&lt;/li&gt;
&lt;li&gt;S3: Secret Access Key =&amp;gt; 發金鑰時拿到的 秘密存取金鑰&lt;/li&gt;
&lt;li&gt;S3: Cdn =&amp;gt; 我們自定義的 CDN 路徑 + {filepath}&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753970359747.webp&#34;
  alt=&#34;1753970361767&#34;width=&#34;2366&#34; height=&#34;1708&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;都搞定之後就可以開個 &lt;code&gt;.md&lt;/code&gt; 檔、複製個圖片，試試看 &lt;code&gt;Shift + Alt + V&lt;/code&gt; 有沒有自動上傳囉～&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;從 Imgur 爆炸到搬完圖片也拖了快兩個月，但幸好有蠻多前人遺跡和好夥伴 GPT 的協助，還算搬得順利。秉持著一個「做都做了」的理念，乾脆把圖床搬家過程也拿出來水一篇，喜得部落格文章數 +1&lt;/p&gt;
&lt;p&gt;搬完之後想說圖片也壓縮了、R2 也有快取之類的，是不是再來跑一次 PageSpeed Insights，分數意外地比之前高不少，也算是一個意外撿到的驚喜了&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1753971346630.webp&#34;
  alt=&#34;1753971347624&#34;width=&#34;1894&#34; height=&#34;922&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.kyomind.tw/weekly-review-43/&#34;&gt;Imgur 封鎖台灣 IP，我把圖床搬到 Cloudflare R2 - Code and Me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://eyewithouts.com/r2&#34;&gt;使用 Cloudflare R2 當作遠端圖床 - Eyewithouts 韋觀&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ivonblog.com/posts/cloudflare-r2-image-hosting/&#34;&gt;架設 Cloudflare R2 免費圖床，給 Hugo 靜態網站託管圖片 · Ivon的部落格&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.markkulab.net/2021/08/06/powershell-webp-conveter/&#34;&gt;透過 PowerShell 製作 JPG/ PNG 轉 webp 小工具 (使用 Google webp converter lib) | Mark Ku&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@wendyhsinyun/%E7%94%A8%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%A3%93%E7%B8%AE%E5%A4%9A%E5%BC%B5-webp-%E7%85%A7%E7%89%87-8da81540f93e&#34;&gt;用命令行一次壓縮多張 WebP 照片 - HY - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.shopify.com/blog/what-is-webp-file?utm_source=chatgpt.com&#34;&gt;What Is a WebP File? How WebP Compares To JPEG and PNG (2025) - Shopify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cunoe.com/blog/markdown-image-extension&#34;&gt;使用 Markdown Image 扩展实现上传图片到 Cloudflare R2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.hao.kim/archives/hugo-vscode-auto-upload-file-to-oss-r2&#34;&gt;Hugo vscode 自动上传图片到cloudflare | Hao DevSecOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.cloudflare.com/r2/api/tokens/&#34;&gt;Authentication · Cloudflare R2 docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.cloudflare.com/r2/objects/upload-objects/&#34;&gt;Upload objects · Cloudflare R2 docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://community.cloudflare.com/t/custom-domain-for-r2-bucket-not-hosted-on-cloudflare/608734?utm_source=chatgpt.com&#34;&gt;Custom domain for R2 bucket not hosted on Cloudflare - Developers / Images - Cloudflare Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 使用 PDFSharp 在 PDF 加上浮水印和檔案密碼吧</title>
      <link>https://igouist.github.io/post/2025/07/dotnet-add-watermark-and-password-to-pdf-using-pdfsharp/</link>
      <pubDate>Mon, 07 Jul 2025 16:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/07/dotnet-add-watermark-and-password-to-pdf-using-pdfsharp/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/0651b31b-6182-4988-bc4e-026afdbf34b0_11zon.jpg&#34;
  alt=&#34;&#34;width=&#34;1536&#34; height=&#34;1024&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接續前一篇 &lt;a href=&#34;https://igouist.github.io/post/2025/05/csharp-convert-html-to-pdf-using-dinktopdf/&#34;&gt;C#: 使用 DinkToPdf 把 HTML 轉成 PDF&lt;/a&gt;，這篇要來用 PDFSharp 對 PDF 檔案進行一些小小的調整。基本上就是紀錄一下這次遇到的需求和一些坑，主要的內容會有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;替 PDF 加上浮水印&lt;/li&gt;
&lt;li&gt;實作 FontResolver 以支援中文字，避免 ⎕⎕⎕&lt;/li&gt;
&lt;li&gt;替 PDF 加上檔案密碼&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;首先當然是要先安裝 &lt;a href=&#34;https://www.nuget.org/packages/PDFsharp&#34;&gt;PDFSharp&lt;/a&gt; 套件：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PixPin_2025-07-07_15-54-50_11zon.jpg&#34;
  alt=&#34;&#34;width=&#34;885&#34; height=&#34;124&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接續上次的進度，我們把 PDF 檔案弄成了一組 &lt;code&gt;byte[]&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 在上一篇文章，我們使用 DinkToPdf 把 Html 轉成了 PDF 檔案的 byte[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pdfBytes = converter.Convert(doc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 或是打開已存在的 PDF 檔案（留著讓我以後複製貼上）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// var pdfBytes = File.ReadAllBytes(@&amp;#34;C:\temp\output.pdf&amp;#34;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 PdfSharp 由記憶體串流讀取轉換後的 PDF，開啟模式設定為 Modify（可以進行修改）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var document = PdfReader.Open(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MemoryStream(pdfBytes), PdfDocumentOpenMode.Modify);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在我們已經用 PDFSharp 開啟這個 PDF 檔案了，接著就讓我們從加上簡單的浮水印開始吧！&lt;/p&gt;
&lt;h2 id=&#34;替-pdf-加上浮水印&#34;&gt;替 PDF 加上浮水印&lt;/h2&gt;
&lt;p&gt;加浮水印的時候要注意幾個小部份：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PDF 是逐頁構成的，所以要 foreach &lt;code&gt;document.Pages&lt;/code&gt; 一頁一頁加上去&lt;/li&gt;
&lt;li&gt;浮水印的原理是用繪圖工具把字畫上去，繪圖點的預設位置是座標 (0,0)，如果浮水印要壓成斜的話要記得先旋轉&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下採用直接對程式碼進行註解的說明方式，&lt;br/&gt;朋朋們也可以對照參考官方的 Sample：&lt;a href=&#34;https://pdfsharp.net/wiki-1.5/Print.aspx?Page=Watermark-sample&#34;&gt;PDFsharp Sample: Watermark&lt;/a&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; watermarkContent = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;waterwater&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 記得用 EmbedCompleteFontFile 把字體嵌進去 PDF 避免客戶亂碼&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//（ps: 大家常用的 Always 選項已經被淘汰了）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 但要注意嵌入字體會讓 PDF 檔案變大&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; font = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; XFont(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    familyName: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Arial&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    emSize: &lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    style: XFontStyleEx.Regular,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pdfOptions: &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; XPdfFontOptions(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        PdfFontEncoding.Unicode, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        PdfFontEmbedding.EmbedCompleteFontFile));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 建立一個半透明的筆刷來繪製浮水印&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// XColor.FromArgb(50, 0, 0, 0) 表示透明度 50（0~255），顏色為黑色 (0, 0, 0)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; brush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; XSolidBrush(XColor.FromArgb(&lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 注意：每一頁都是單獨的 Page，所以浮水印要一頁一頁加&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;foreach&lt;/span&gt; (PdfPage page &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; document.Pages)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 浮水印需要用繪圖工具，所以先從當前頁面開一個 XGraphics&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 其中 XGraphicsPdfPageOptions 的 Append 和 Prepend 會影響浮水印在現有物件前方或後方&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 可參考: https://github.com/Lakerfield/PdfSharp/blob/master/PDFsharp/code/PdfSharp/PdfSharp.Drawing/enums/XGraphicsPdfPageOptions.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Append);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 取得當前頁面的寬度與高度，以便計算頁面的中心點&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 因為旋轉會以畫面中央為中心處理，因此要將繪圖點先移動到頁面的中央再進行旋轉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 確保後續加浮水印文字的角度正確&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 可以想像成桌子上放一張 A4 紙，然後用手指戳著一個點，再轉看看那張紙，會比較有畫面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pageWidth = page.Width;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pageHeight = page.Height;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    gfx.TranslateTransform(pageWidth / &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, pageHeight / &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    gfx.RotateTransform(-&lt;span style=&#34;color:#ae81ff&#34;&gt;45&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 旋轉完之後，將繪圖點移回原本的位置（0, 0），準備壓字上去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    gfx.TranslateTransform(-pageWidth / &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, -pageHeight / &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 使用 DrawString 方法在頁面中央繪製浮水印文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 建立一個覆蓋整個頁面的 XRect，並以 Center 對齊格式顯示文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    gfx.DrawString(watermarkContent, font, brush, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; XRect(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, pageWidth, pageHeight), XStringFormats.Center);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後存檔測一下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 儲存 PDF 文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fileName = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;TestPdf_{DateTime.Now.ToString(&amp;#34;&lt;/span&gt;yyyyMMdd_HHmmss&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)}.pdf&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; outputPath = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;C:\temp\&amp;#34;&lt;/span&gt; + fileName;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;document.Save(outputPath);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;實作-fontresolver-以支援中文字&#34;&gt;實作 FontResolver 以支援中文字&lt;/h2&gt;
&lt;p&gt;上面我們替 PDF 加上了浮水印，但浮水印有包含中文字的朋友可能會注意到中文都變成「⎕⎕⎕」了&lt;/p&gt;
&lt;p&gt;這是因為 PDFSharp 在 Core 的版本預設只支援部分英文字體（它想要跨平台，但它沒法知道你用的平台都有啥字體），&lt;strong&gt;要讓 PDFSharp 使用中文字體的話，就要自行實作 FontResolver 並掛到 GlobalFontSettings 上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這部份可以參照官方文件 &lt;a href=&#34;https://docs.pdfsharp.net/PDFsharp/Topics/Fonts/Font-Resolving.html&#34;&gt;Font-Resolvin&lt;/a&gt; 的說明。&lt;br/&gt;同樣地，這邊也留一份程式碼作為示範（不然我以後上哪複製？）&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;首先實作字型解析用的 &lt;code&gt;CustomFontResolver&lt;/code&gt;，以標楷體為例：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 自訂字型解析器實作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 沒做這個的話，中文會變成 ⎕⎕⎕&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// see: https://docs.pdfsharp.net/link/font-resolving.html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CustomFontResolver&lt;/span&gt; : IFontResolver
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; KaiFontKey = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;KAIU&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;[] GetFont(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; faceName)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; fontPath = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;C:\Fonts\kaiu.ttf&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 記得要改成字型檔案路徑&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (File.Exists(fontPath))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; File.ReadAllBytes(fontPath);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileNotFoundException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;找不到字型檔案：&amp;#34;&lt;/span&gt; + fontPath);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; FontResolverInfo ResolveTypeface(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; familyName, &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; isBold, &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; isItalic)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (familyName.Equals(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;標楷體&amp;#34;&lt;/span&gt;, StringComparison.OrdinalIgnoreCase))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FontResolverInfo(KaiFontKey);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 如果之後要支援其他字型，可以註冊在這邊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 都找不到的時候就回去串內建的 PlatformFontResolver&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著在做浮水印之前做一個 &lt;code&gt;GlobalFontSettings&lt;/code&gt; 的註冊和防呆，並且調整一下我們 &lt;code&gt;XFont&lt;/code&gt; 的字型：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; watermarkContent = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;測試_中文字浮水印_浮水浮水&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 注意: 如果使用的字型是中文字，因為 PDFSharp 在 Core 的版本預設只支援部分英文字體（它沒法知道你用的平台都有啥字體）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 中文字體之類的會要求自訂字體解析，因此要自行實作 FontResolver 並掛到 GlobalFontSettings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// see: https://docs.pdfsharp.net/link/font-resolving.html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (GlobalFontSettings.FontResolver &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    GlobalFontSettings.FontResolver = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CustomFontResolver();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 警告：這邊的 familyName（&amp;#34;標楷體&amp;#34;）必須和 FontResolver 的字體名稱一致，可以用 Const string 做一個強制關聯&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; font = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; XFont(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    familyName: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;標楷體&amp;#34;&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    emSize: &lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    style: XFontStyleEx.Regular,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pdfOptions: &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; XPdfFontOptions(PdfFontEncoding.Unicode, PdfFontEmbedding.EmbedCompleteFontFile));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 略......&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣中文字就可以正常顯示囉～&lt;/p&gt;
&lt;h2 id=&#34;替-pdf-加上檔案密碼&#34;&gt;替 PDF 加上檔案密碼&lt;/h2&gt;
&lt;p&gt;除了浮水印以外，PDF 加上密碼也是很常見的需求，這篇也一併紀錄一下。&lt;/p&gt;
&lt;p&gt;在 PDFSharp 加上密碼還蠻簡單的，只需要調整 &lt;code&gt;SecuritySettings&lt;/code&gt; 就好：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 3. 加密 PDF 文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// see: https://stackoverflow.com/questions/12383409/password-protecting-a-pdf-file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;document.SecuritySettings.UserPassword = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;123&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;document.SecuritySettings.OwnerPassword = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;123&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UserPassword&lt;/code&gt; 指的是開啟檔案時要輸入的密碼&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OwnerPassword&lt;/code&gt; 則是列印、複製等操作時需要的密碼&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但一般來說會習慣設成同一組，讓使用者可以進行完整的操作。&lt;/p&gt;
&lt;h2 id=&#34;小結--碎碎念&#34;&gt;小結 &amp;amp; 碎碎念&lt;/h2&gt;
&lt;p&gt;和上一篇 &lt;a href=&#34;https://igouist.github.io/post/2025/05/csharp-convert-html-to-pdf-using-dinktopdf/&#34;&gt;DinkToPdf&lt;/a&gt; 搭配，完成了這次「產製 PDF，並加上浮水印和密碼」的功能需求。&lt;/p&gt;
&lt;p&gt;雖然需求本身並不難，能使用的工具也很多，但前期準備的時候還是踩了一些坑。主要是舊專案採用的 iText 已經改為收費授權，不能無腦照抄移植。接著就有了在上一篇也碎碎念過的這段：&lt;/p&gt;
&lt;p&gt;原本想說用團隊成員碰過的 PDFSharp 三兩下收工，搜了一下也發現了 PdfSharpCore，那就來個 PdfSharpCore + HtmlRendererCore.PdfSharp 經典組合。結果裝下來發現這兩顆的依賴項目 SixLabors.ImageSharp 1.0.4 直接跳一堆弱點，嚇到不敢用。&lt;/p&gt;
&lt;p&gt;幸好 PdfSharp 本體已經直接支援 .Net 8 了，最終決定使用本家 PdfSharp 進行開發，然後用 DinkToPdf 替代掉停止維護的 HtmlRenderer.PdfSharp，兜了個馬車，筆記一篇。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/pdfsharp&#34;&gt;.NET 小技巧 - 使用 PdfSharp / PdfSharpCore 合併 PDF、加浮水印 - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hackmd.io/@CloudyWing/ryhZuhFrp&#34;&gt;「TheArtOfDev.HtmlRenderer.PdfSharp」的踩雷歷程 - CloudyWing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.pdfsharp.net/PDFsharp/Overview/About.html&#34;&gt;Overview of PDFsharp (Library)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>.Net: 善用 IServiceCollection Extension 和自製 Builder，讓服務註冊更有約束吧</title>
      <link>https://igouist.github.io/post/2025/06/dotnet-using-iservicecollection-extensions-to-enforce-registration-constraints/</link>
      <pubDate>Sun, 29 Jun 2025 15:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/06/dotnet-using-iservicecollection-extensions-to-enforce-registration-constraints/</guid>
      <description>&lt;p&gt;在之前的&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;依賴注入文章&lt;/a&gt;的「組合根請稍作分類」小節，我們介紹過使用 IServiceCollection 的擴充方法來對要註冊的服務進行分類和管理的做法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ServiceCollectionExtensions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 註冊 Nice Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; IServiceCollection AddNiceServices(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		services.AddScoped&amp;lt;INiceService, NiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...一些相關的註冊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; services;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddNiceServices();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一直以來我都採用這個方法來簡單地拆分我的服務註冊，在大多數的場合已經足夠使用（尤其是只關注目前的專案時）。&lt;/p&gt;
&lt;p&gt;但前幾天在看某個套件的實作時，開發的朋朋跟我分享了一些延伸的做法，感覺合理又常見，屬於有注意到就會記得的小技巧，決定馬上來記錄一篇筆記。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;場景是這樣的：當我們在開發套件的時候，常常會有主要套件跟擴充用的延伸套件。&lt;/p&gt;
&lt;p&gt;例如說，我可能會有一個主要的 &lt;code&gt;NiceTool&lt;/code&gt; 套件，跟擴展前者的 &lt;code&gt;NiceTool.Memory&lt;/code&gt; 套件&lt;/p&gt;
&lt;p&gt;這兩個套件都會需要對 IServiceCollection 註冊一些服務，並且延伸套件 &lt;code&gt;NiceTool.Memory&lt;/code&gt; 需要確認 &lt;code&gt;NiceTool&lt;/code&gt; 的服務都有進行註冊才能正常運作。&lt;/p&gt;
&lt;p&gt;如果我們想要先確保主要套件 &lt;code&gt;NiceTool&lt;/code&gt; 的服務都註冊了，再處理 &lt;code&gt;NiceTool.Memory&lt;/code&gt;，同時又想保留擴充組合的彈性給以後的 &lt;code&gt;NiceTool.SqlServer&lt;/code&gt;，甚至我們可能想在兩者各自的註冊加點工、傳遞點資訊，可以怎麼辦呢？&lt;/p&gt;
&lt;p&gt;這時候就可以考慮&lt;strong&gt;包一個自家的 Builder，用這個 Builder 來把這些套件給串起來&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以上面的例子來說，我可以製作一個 &lt;code&gt;INiceToolBuilder&lt;/code&gt;，並且把服務註冊時要用到的 &lt;code&gt;IServiceCollection&lt;/code&gt; 包在裡面：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;INiceToolBuilder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	IServiceCollection Services { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NiceToolBuilder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IServiceCollection Services { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; NiceToolBuilder(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Services = services;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著，在作為註冊起點的主要套件 &lt;code&gt;NiceTool&lt;/code&gt; 裡，我就可以提供一個產生 &lt;code&gt;INiceToolBuilder&lt;/code&gt; 的擴充方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; INiceToolBuilder AddNiceTools(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;INiceService, NiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// ...一些相關的註冊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; builder = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NiceToolBuilder(services);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; builder;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著，在延伸的 &lt;code&gt;NiceTool.Memory&lt;/code&gt;，我就可以接續著使用 &lt;code&gt;INiceToolBuilder&lt;/code&gt; 來對 &lt;code&gt;IServiceCollection&lt;/code&gt; 進行服務註冊：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; INiceToolBuilder AddNiceMemoryTools(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; INiceToolBuilder builder)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 對 INiceToolBuilder 帶進來的 IServiceCollection 繼續進行服務註冊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	builder.Services.AddScoped&amp;lt;INiceMemoryService, NiceMemoryService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// ...一些相關的註冊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; builder;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一來，我們就可以&lt;strong&gt;明示&lt;/strong&gt;使用者在註冊的時候，先把主要套件提供的服務註冊方法呼叫完並取得 &lt;code&gt;INiceToolBuilder&lt;/code&gt;，再進行延伸套件的服務註冊。如果有什麼資訊需要帶著一起走，也可以直接包在裡面就好。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;Program.cs&lt;/code&gt; 也可以用我們熟悉的串串樂舒暢地一路串完，並讓使用者自行組合：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.AddNiceServices()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.AddNiceMemoryTools();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;是不是看起來乾淨舒服，又方便有約束力呢。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;為什麼會說這是「有注意到就會記得的小技巧」呢？因為這個做法其實還蠻常見的，尤其是各種套件和工具。&lt;/p&gt;
&lt;p&gt;例如大家應該都很熟悉的 &lt;code&gt;AddHealthChecks()&lt;/code&gt;，回傳的就是 &lt;code&gt;IHealthChecksBuilder&lt;/code&gt;，後續就能接著去呼叫另一顆套件的 &lt;code&gt;AddSqlServer()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;又或者是我們 &lt;a href=&#34;https://igouist.github.io/post/2024/08/dotnet-ioptions/&#34;&gt;IOptions 文章&lt;/a&gt;介紹過的 &lt;code&gt;AddOptions()&lt;/code&gt;，回傳的是 &lt;code&gt;OptionsBuilder&lt;/code&gt;；&lt;br/&gt;隔壁的 &lt;code&gt;AddLogging()&lt;/code&gt; 提供傳入 &lt;code&gt;ILoggingBuilder&lt;/code&gt; 的委派作為參數等等……&lt;/p&gt;
&lt;p&gt;如果只是一路 &lt;code&gt;Add()&lt;/code&gt; &lt;code&gt;Use()&lt;/code&gt; &lt;code&gt;Add()&lt;/code&gt; &lt;code&gt;Use()&lt;/code&gt;，可能就不會注意到這個設計上的小巧思。&lt;/p&gt;
&lt;p&gt;像我個人以前比較少做套件，大多時候都在服務內處理，習慣 IServiceCollection 幹到底，就不曾留心在這部份過。這次有朋朋跟我分享了這個眉角，馬上跟之前註冊各種套件工具時的記憶連了起來。&lt;/p&gt;
&lt;p&gt;延伸想了一下，同樣的概念也不是只有套件開發的時候會用到，那不如記錄下來，以後才方便抄(?)&lt;/p&gt;
&lt;p&gt;如此如此，這般這般，又成功靠偷朋朋的小技巧水了一篇，功德圓滿，阿彌陀佛。&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Imgur 一直 temporarily over capacity 嗎？先檢查網路看看吧</title>
      <link>https://igouist.github.io/post/2025/06/imgur-temporarily-over-capacity-maybe-your-ip-banned/</link>
      <pubDate>Sat, 14 Jun 2025 13:30:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/06/imgur-temporarily-over-capacity-maybe-your-ip-banned/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;直接先講結論，給正在查這條錯誤訊息而來到這邊的朋朋參考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先看 &lt;a href=&#34;https://status.imgur.com/&#34;&gt;imgur status&lt;/a&gt; 確定 Imgur 服務有沒有正常&lt;/li&gt;
&lt;li&gt;優先判斷是不是網路問題：如果有其他網路，更換網路試試看&lt;/li&gt;
&lt;li&gt;如果開著 VPN，關掉再試試看； 反之如果沒開 VPN，找一組跳去外國試看看&lt;/li&gt;
&lt;li&gt;到這邊還是不行的話，代表你遇到的狀況跟我不一樣，就，祝你好運&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;ldquo;Imgur is temporarily over capacity&amp;rdquo; 這組錯誤訊息除了服務異常，也有可能是 IP 或地區被阻擋。目前（2025/06/14）在 &lt;a href=&#34;https://www.ptt.cc/bbs/Gossiping/M.1747406974.A.003.html&#34;&gt;PTT&lt;/a&gt; 和&lt;a href=&#34;https://home.gamer.com.tw/creationDetail.php?sn=6146343&#34;&gt;巴哈&lt;/a&gt;也有台灣 IP 被擋（官方未證實）的討論。&lt;br/&gt;因此有遇到並且找到這篇文的朋友，請先從網路問題開始檢查，最好能用 VPN 之類的跳到國外驗證試試，祝順利。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;前陣子寫文章要上傳圖片的時候一直無法上傳，覺得奇怪就去 Imgur 看了一下，結果登入按了啥反應也沒有，忘記密碼也沒反應。&lt;/p&gt;
&lt;p&gt;開 F12 只看到 &amp;ldquo;Imgur is temporarily over capacity. Please try again later.&amp;quot;，例如忘記密碼的回應：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;error&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Imgur is temporarily over capacity. Please try again later.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;奇怪的是用手機的 Imgur 可以正常登入，但換成筆電仍然是不能登入。但既然錯誤訊息都說它們已經 over capacity 了，這點輕微的靈異現象應該也都是合理的（吧）&lt;/p&gt;
&lt;p&gt;本來想說「那就等等唄，誰系統沒壞過」，秉持一份工程師不為難工程師的善心，大不了就等系統恢復再用就好&lt;/p&gt;
&lt;p&gt;但幾天過去了，感覺事情越來越不對勁……&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;身為工程師，第一步當然是要確認服務到底有沒有活著。&lt;/p&gt;
&lt;p&gt;翻了一下 &lt;a href=&#34;https://status.imgur.com/&#34;&gt;imgur status&lt;/a&gt; 全都是綠燈，寄信給客服也沒有下落，決定認命上網查看看&lt;/p&gt;
&lt;p&gt;結果爬了幾篇討論，突然發現這個「Imgur is temporarily over capacity」好像不是單純的「temporarily over capacity」：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/techsupport/comments/1g2nao7/imgur_is_temporarily_over_capacity_please_try/&#34;&gt;&amp;ldquo;Imgur is temporarily over capacity. Please try again later.&amp;rdquo; How do I solve it?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/PrivateInternetAccess/comments/zk9hsa/imgur_is_temporarily_over_capacity_please_try/&#34;&gt;&amp;ldquo;Imgur is temporarily over capacity. Please try again later.&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/67376404/imgur-blocking-my-ip-works-completely-fine-on-my-amplify-site-but-when-testing&#34;&gt;Imgur blocking my IP? Works completely fine on my Amplify site, but when testing locally I get &amp;ldquo;Imgur is temporarily over capacity&amp;hellip;&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;從上面幾篇可以看到：有些人把 VPN 關閉後就可以正常訪問了，或是如果標 &lt;code&gt;localhost&lt;/code&gt; 也會遇到同樣的錯誤。&lt;/p&gt;
&lt;p&gt;再翻了幾篇之後，感覺許多人吃這組錯誤訊息的狀況是：IP 之類的被 Imgur Ban 了（不是，你不讓我進去，找藉口跟我說沒位置了，然後送我 500？）&lt;/p&gt;
&lt;p&gt;下個問題就來了：俺尋思俺也沒開著 VPN 啊，更何況我 &lt;s&gt;偷懶這麼久&lt;/s&gt; 近期也沒使用 Imgur，手機 APP 又登得進去，應該不是針對我的帳號做阻擋了。那到底是啥被擋了？&lt;/p&gt;
&lt;p&gt;這時突然看到巴哈這篇 &lt;a href=&#34;https://home.gamer.com.tw/creationDetail.php?sn=6146343&#34;&gt;Imgur壞了? 無法上傳? 2025最佳替代圖床&lt;/a&gt; 提到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;全球最大圖床之一的 Imgur 是許多網友的最熟悉放圖工具之一，大約從 2025 年 5 月 16 日上午開始，Imgur 開始針對台灣地區的 ip 進行封鎖，使用國外 ip 可以，原因尚未對外公開說明，截至今日仍處於無法上傳的情況……&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;看到這句決定開一下 VPN 跳外國試試看，結果&lt;/p&gt;
&lt;p&gt;登入成功。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;這次的找路過程就記錄到這邊。順便把上面的流程做個小整理，提供給同樣遇到 Imgur 經典錯誤 &lt;code&gt;&amp;quot;Imgur is temporarily over capacity. Please try again later.&amp;quot;&lt;/code&gt; 的朋友參考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先看 &lt;a href=&#34;https://status.imgur.com/&#34;&gt;imgur status&lt;/a&gt; 確定 Imgur 服務有沒有正常&lt;/li&gt;
&lt;li&gt;優先判斷是不是網路問題：如果有其他網路，更換網路試試看&lt;/li&gt;
&lt;li&gt;如果開著 VPN，關掉再試試看； 反之如果沒開 VPN，找一組跳去外國試看看&lt;/li&gt;
&lt;li&gt;到這邊還是不行的話，代表你遇到的狀況跟我不一樣，就，祝你好運&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;剩下的問題就是後續該怎麼辦了。&lt;/p&gt;
&lt;p&gt;雖然目前開著 VPN 還是可以正常上傳圖片、繼續寫作，然而這次狀況來得突然，也開始對能不能長久使用這個平台有點疑慮……&lt;/p&gt;
&lt;p&gt;稍微逛了一圈，&lt;a href=&#34;https://home.gamer.com.tw/creationDetail.php?sn=6146343&#34;&gt;有些人&lt;/a&gt;推薦了 meee、postimages 之類的圖床，也&lt;a href=&#34;https://forum.gamer.com.tw/C.php?bsn=60030&amp;amp;snA=668936&#34;&gt;有些人&lt;/a&gt;跟我一樣選擇先繞過去。&lt;/p&gt;
&lt;p&gt;其中感覺最可行的是這篇：&lt;a href=&#34;https://blog.kyomind.tw/weekly-review-43/&#34;&gt;Imgur 封鎖台灣 IP，我把圖床搬到 Cloudflare R2 - Code and Me&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;流量免費、可長期運作，而且最適合我原本 VSCode 打開就一鍵無腦上傳圖片的習慣。&lt;/p&gt;
&lt;p&gt;但想到要搬圖片搞環境就好懶，開 VPN 只需要按一個鍵…（我們拖延症重度患者就是這樣的）&lt;/p&gt;
&lt;p&gt;最後決定先把自家的圖片都爬一份下來再說，等哪個連假心情好再來處理吧，阿彌陀佛。&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Windows: 關閉 OneDrive 同步，並把我的文件移回預設路徑</title>
      <link>https://igouist.github.io/post/2025/06/disable-onedrive-sync-and-restore-documents-folder/</link>
      <pubDate>Sat, 14 Jun 2025 09:30:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/06/disable-onedrive-sync-and-restore-documents-folder/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rSAp9bP.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;600&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最近因緣際會必須重灌電腦，安裝過程中忘記把 OneDrive 取消掉，結果進 Windows 才發現我的文件夾跑去 &lt;code&gt;C:\Users\xxx\OneDrive\文件&lt;/code&gt;，差點昏過去囧&lt;/p&gt;
&lt;p&gt;先不論我一堆遊戲存檔都在 Documents 資料夾裡，預設的 5GB 根本吃不下（硬吃還會導致信箱不能收信），一些自己寫的小腳本都已經把 &lt;code&gt;C:\Users\xxx\Documents&lt;/code&gt; 當作「我的文件」的預設路徑了，實在不想為了 OneDrive 調整…&lt;/p&gt;
&lt;p&gt;因此決定留一篇筆記，把「我的文件」從 &lt;code&gt;C:\Users\xxx\OneDrive\文件&lt;/code&gt; 搬回 &lt;code&gt;C:\Users\xxx\Documents&lt;/code&gt;，給下次重灌的我（對，我下次還是會忘記的），順便也給網路上遇到同樣問題的朋朋跟他們的 AI Search 參考。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：如果你是遇到 OneDrive 名稱自動帶了空格跟中文（例如 &amp;ldquo;OneDrive - 超棒公司&amp;rdquo;，像是&lt;a href=&#34;https://answers.microsoft.com/zh-hans/msoffice/forum/all/onedrive-site/1f7451cb-63e4-4337-8e1a-e85abafb00ba?utm_source=chatgpt.com&#34;&gt;微軟社區的這個問題&lt;/a&gt;）導致一堆路徑沒加引號的腳本炸掉，但又不打算關閉 OneDrive 同步功能的話，只需要用改名的方式來迴避就可以了。請參考 &lt;a href=&#34;https://blog.csdn.net/BattleMasterTank/article/details/135646862&#34;&gt;更改 OneDrive 預設資料夾名稱&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&#34;一關閉資料夾同步&#34;&gt;一、關閉資料夾同步&lt;/h3&gt;
&lt;p&gt;點開 OneDrive -&amp;gt; 右上角的齒輪 -&amp;gt; 設定 -&amp;gt; 同步與備份 -&amp;gt; 把文件之類的同步關一關&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Kkvylv9.webp&#34;
  alt=&#34;Image&#34;width=&#34;1704&#34; height=&#34;1484&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;二取消連結此電腦&#34;&gt;二、取消連結此電腦&lt;/h3&gt;
&lt;p&gt;到 OneDrive 的帳戶畫面，用力按下「取消連結此電腦」，告訴它「你不要再管我了！』&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Xzvebsu.webp&#34;
  alt=&#34;Image&#34;width=&#34;1730&#34; height=&#34;1200&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;三將我的文件丟回預設路徑&#34;&gt;三、將「我的文件」丟回預設路徑&lt;/h3&gt;
&lt;p&gt;在檔案總管，對「文件」右鍵 -&amp;gt; 內容 -&amp;gt; 位置 -&amp;gt; 還原為預設值&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ed1Jfh1.webp&#34;
  alt=&#34;Image&#34;width=&#34;984&#34; height=&#34;998&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就移回去囉！一二三，One Drive 掰👋&lt;/p&gt;
&lt;h3 id=&#34;參考資料&#34;&gt;參考資料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/WindowsHelp/comments/13ng4wk/solved_moving_documents_pictures_and_other&#34;&gt;[SOLVED] Moving Documents, Pictures and other default folders away from OneDrive and back into their normal location - reddit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.elevenforum.com/t/how-to-move-documents-folder-from-onedrive-to-local-turn-off-sync-with-onedrive.27311/&#34;&gt;How to move Documents folder from OneDrive to local / turn off sync with OneDrive | Windows 11 Forum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 使用 DinkToPdf 把 HTML 轉成 PDF 吧</title>
      <link>https://igouist.github.io/post/2025/05/csharp-convert-html-to-pdf-using-dinktopdf/</link>
      <pubDate>Mon, 05 May 2025 22:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/05/csharp-convert-html-to-pdf-using-dinktopdf/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/y3Oofpkl.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;427&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最近做的一項需求是要把 HTML 轉換成 PDF，過程中決定使用 &lt;a href=&#34;https://github.com/rdvojmoc/DinkToPdf&#34;&gt;DinkToPdf&lt;/a&gt; 來處理這一段。&lt;/p&gt;
&lt;p&gt;考慮到現在拿到的一些文件模板都是 HTML 檔了，感覺以後會蠻常碰到這個場景，決定來筆記一篇，給未來的我複製貼上。&lt;/p&gt;
&lt;h2 id=&#34;環境準備&#34;&gt;環境準備&lt;/h2&gt;
&lt;p&gt;首先，當然是要先到 Nuget 安裝這篇的主角：&lt;a href=&#34;https://www.nuget.org/packages/DinkToPdf&#34;&gt;DinkToPdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vdQqBOll.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;110&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;由於 DinkToPdf 只負責包裝給 C# 這段，&lt;strong&gt;實際上要產出 PDF 還得用到 wkhtmltopdf 這個工具&lt;/strong&gt;，因此還需要先弄到 wkhtmltopdf 的檔案。&lt;/p&gt;
&lt;p&gt;不過作者也知道大家不是很想另外跑去找，所以 wkhtmltopdf 的組件檔案可以直接從 &lt;a href=&#34;https://github.com/rdvojmoc/DinkToPdf/tree/master/v0.12.4&#34;&gt;DinkToPdf 的 Repo&lt;/a&gt; 拿。但要記得要按照作業系統下載對應的 wkhtmltopdf 檔案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows =&amp;gt; libwkhtmltox.dll&lt;/li&gt;
&lt;li&gt;Linux =&amp;gt; libwkhtmltox.so&lt;/li&gt;
&lt;li&gt;macOS =&amp;gt; libwkhtmltox.dylib&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;del&gt;但全部都載也不會有人阻止你就是了。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;最後把 libwkhtmltox 放到專案的根目錄底下，DinkToPdf 就呼叫得到囉！&lt;br/&gt;（問就是魔法，不服的自己去啃 &lt;a href=&#34;https://learn.microsoft.com/zh-tw/dotnet/standard/native-interop/pinvoke&#34;&gt;P/Invoke&lt;/a&gt;）&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;libwkhtmltox 準備好之後，因為我的示範環境是 .Net 8 的 API，所以還需要註冊 DinkToPdf 的 &lt;code&gt;IConverter&lt;/code&gt; 到 DI 框架上。&lt;/p&gt;
&lt;p&gt;這邊額外說明一下，&lt;code&gt;IConverter&lt;/code&gt; 其實有兩個版本的實作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BasicConverter&lt;/code&gt;：最直接的簡易版本，會直接去呼叫 libwkhtmltox&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SynchronizedConverter&lt;/code&gt;：有搞執行緒安全的版本，會讓任務排隊依序處理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但通常我們就直接參考官方給的範例無腦註冊 &lt;code&gt;SynchronizedConverter&lt;/code&gt; 就行了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddSingleton&amp;lt;IConverter&amp;gt;(_ =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SynchronizedConverter(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; PdfTools()));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊要特別提醒一點：&lt;strong&gt;DinkToPdf 的 IConverter 一定要註冊成單例！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即使今天不是使用 DI 框架來注入的話，也一定要把呼叫的 Converter 包裝成單例，否則會導致多個 Converter 去操作 libwkhtmltox 而發生錯誤。&lt;/p&gt;
&lt;p&gt;如果在使用 DinkToPdf 的時候，發現產生的 PDF 會有「一次正常、一次跑版、一次正常、一次跑版」之類的靈異現象，通常就是沒有把 Converter 包成單例所造成的組件呼叫錯誤。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果懶得找檔案搞單例什麼的，也可以像我一樣，直接找人家包好的工具爽爽用：&lt;/p&gt;
&lt;p&gt;這邊我選擇直接開 Nuget 安裝 &lt;a href=&#34;https://www.nuget.org/packages/HtmlToPdfConverter/&#34;&gt;HtmlToPdfConverter&lt;/a&gt;，它已經把 wkhtmltopdf 組件包含在套件裡了，還封裝了一層。我們只需要在 DI 註冊時加上：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddHtmlToPdfConverter();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它就會自己呼叫 &lt;code&gt;RuntimeInformation.IsOSPlatform&lt;/code&gt; 來載入對應的 libwkhtmltox 組件檔案了。（有興趣的朋友也可以參考 &lt;a href=&#34;https://github.com/Cr3ature/HtmlToPdfConverter/blob/master/Source/HtmlToPdfConverter.Core/Configuration/ServiceCollectionExtenstion.cs&#34;&gt;HtmlToPdfConverter Repo 的寫法&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;不管是選擇自己放組件，還是直接挖人家包好的。總之材料準備好之後，就可以開工啦！&lt;/p&gt;
&lt;h2 id=&#34;實作紀錄&#34;&gt;實作紀錄&lt;/h2&gt;
&lt;p&gt;首先，假設我們有某組 HTML 內容：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; GetHtmlContent()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 為了簡單示範，直接寫死一組字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; html =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;    &amp;lt;!DOCTYPE html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;html lang=&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-Hant&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;meta charset=&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;title&amp;gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;測試&lt;/span&gt; DinkToPdf&amp;lt;/title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;style&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            body {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                font-family: &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;標楷體&amp;#39;&lt;/span&gt;, sans-serif;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                padding: &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;px;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            h1 {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                color: &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;333&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/style&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;h1&amp;gt;Hello world&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;！&lt;/span&gt;&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;p&amp;gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;我來，我見，我&lt;/span&gt; PDF&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;。&lt;/span&gt;&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 我們也有可能是從檔案讀的嘛，留這段方便我複製貼上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// var path = @&amp;#34;C:\temp\test.html&amp;#34;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// var html = File.ReadAllText(path, Encoding.UTF8);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; html;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就是 DinkToPdf 上場的時候，首先我們需要宣告一組 &lt;code&gt;HtmlToPdfDocument&lt;/code&gt;，並設置一些文件相關的特性。&lt;/p&gt;
&lt;p&gt;我們這種最簡單的例子，直接設定 A4 直向給他就可以了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; doc = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HtmlToPdfDocument()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    GlobalSettings = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; GlobalSettings()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        PaperSize = PaperKind.A4,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Orientation = Orientation.Portrait,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;補充：可調整的項目請參考 &lt;a href=&#34;https://github.com/rdvojmoc/DinkToPdf/blob/master/src/DinkToPdf/Settings/GlobalSettings.cs&#34;&gt;GlobalSettings.cs&lt;/a&gt; 的欄位，像是一些彩色／黑白啦、文件大小跟方向等等都可以從這裡調整。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;有了基本的文件之後，就可以直接把我們前面的 HTML 整坨塞進去：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;doc.Objects.Add(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ObjectSettings()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	HtmlContent = html,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	WebSettings = { DefaultEncoding = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	HeaderSettings = { FontName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;標楷體&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;都 OK 之後就可以把 &lt;code&gt;IConverter&lt;/code&gt; 叫出來轉檔案囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 為了方便示範直接 new 一個 SynchronizedConverter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 實際使用時請從 DI 之類的地方取得單例物件重複用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; converter = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SynchronizedConverter(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; PdfTools());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pdfBytes = converter.Convert(doc);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後就可以根據狀況，看是要存成檔案，還是要把 bytes[] 傳遞到下一站：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 把前面產生的 PDF 儲存成檔案&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 如果要繼續對 PDF 進行加工的話（例如後續用 PDFSharp 接手）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 也可以考慮傳遞上面的 pdfBytes 就好&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; outputPath = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;C:\temp\output.pdf&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;File.WriteAllBytes(outputPath, pdfBytes);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;已產生 PDF：{outputPath}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就搞定啦，馬上打開來看看：



&lt;img
  src=&#34;https://image.igouist.net/UZlxDcnl.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;199&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;後話&#34;&gt;後話&lt;/h2&gt;
&lt;p&gt;如果像我一樣拿到 HTML 的樣板，最後要產製 PDF 的話，DinkToPdf 算是個不錯的小工具，尤其程式撰寫的部份蠻簡單的，相關設定丟一丟就可以直接轉換檔案了。&lt;/p&gt;
&lt;p&gt;比起程式碼部份，反而環境設定的坑還比較多，如果沒乖乖丟組件檔案，又或是註冊的時候沒有好好做成單例，就會發生各種怪怪的事情（果然還是留一篇筆記給未來的我抄比較安全）&lt;/p&gt;
&lt;p&gt;原本有考慮 PdfSharp 最常搭配的 HtmlRenderer.PdfSharp，但它已經&lt;a href=&#34;https://github.com/ArthurHub/HTML-Renderer&#34;&gt;停止維護&lt;/a&gt;；而看起來像是移植版本的 HtmlRendererCore.PdfSharp 光用 Linqpad 裝看看就跳出一排東西，實在不敢直接用：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VNVILNjl.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;370&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最後繞了半圈，還是把 libwkhtmltox.dll 丟一丟、DinkToPdf 叫一叫最簡單方便，畢竟早點下班才是正義，阿彌陀佛。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/omage/article/details/114011447&#34;&gt;如何使用 Dinktopdf 在 .net core 项目里将 Html 转成 PDF - CSDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rdvojmoc/DinkToPdf&#34;&gt;DinkToPdf - Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/75534946/second-call-to-dinktopdf-pdf-library-fails&#34;&gt;c# - Second call to dinktopdf pdf library fails - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rdvojmoc/DinkToPdf/issues/44&#34;&gt;Subsequent PdfTools instances yield wrong PDF output · Issue #44 · rdvojmoc/DinkToPdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>《真三國無雙：起源》白金心得</title>
      <link>https://igouist.github.io/post/2025/04/shin-sangoku-musou-origins/</link>
      <pubDate>Sun, 27 Apr 2025 20:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/04/shin-sangoku-musou-origins/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/V5mhb2x.webp&#34;
  alt=&#34;Image&#34;width=&#34;879&#34; height=&#34;402&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;前陣子成功把《真三國無雙：起源》給白金了，按照慣例趁休假來把遊玩紀錄整理成一篇心得吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：這篇是我個人遊玩《真三國無雙：起源》的心得。&lt;br/&gt;
如果你是在找《真三國無雙：起源》的白金獎盃攻略，推薦你這篇：&lt;br/&gt;&lt;a href=&#34;https://steamcommunity.com/sharedfiles/filedetails/?id=3416680226&#34;&gt;《真・三國無雙 起源》全成就指南 - Steam 社群&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;作為一名從２代玩到現在的老玩家，對這次的起源蠻滿意的。&lt;/p&gt;
&lt;p&gt;雖然一開始玩的時候，對於不能像系列作一樣操作各式各樣的無雙武將這點感到很可惜，但隨著遊戲推進，覺得操作一名主角跟這些武將互動、參與戰役也挺有意思的。&lt;/p&gt;
&lt;p&gt;同樣是操作路人甲（？）闖蕩三國，原本還有點擔心會不會變得像《臥龍》，但《無雙》還是保留了很多自家的特色，兩者對戰場的詮釋完全不同，尤其是一騎當千、馳聘戰場的部份很讓人滿意。&lt;/p&gt;
&lt;p&gt;既然「無雙」都已經能作為一種遊戲類型的代稱了，聊無雙就一定會聊到戰鬥，這也是我對本作最滿意的點：&lt;strong&gt;有做出大軍團相撞跟亂軍衝殺的感覺&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;戰場體驗和地圖設計&#34;&gt;戰場體驗和地圖設計&lt;/h3&gt;
&lt;p&gt;這點我對&lt;a href=&#34;https://x.com/JoeChang2022/status/1884123274172670322&#34;&gt;張國洋大大發的推&lt;/a&gt;很有共鳴，尤其是「戰役裡樂趣跟緊張感的平衡」這一項。這一代主線戰役裡幾乎都有明確的目標、各種苦戰的友軍、刺激緊張的橋段。在虎牢關等戰役裡更有超讚的演出（最後轉角大軍衝鋒搭配敵軍投石的震撼感真的很棒），沉浸感給得滿滿滿，身為無雙粉真的會感動到。&lt;/p&gt;
&lt;p&gt;敵兵的數量給得很大方，爽快割草的特色有確實保留下來。但同時戰場又有各種事件和突發劇情（例如呂布追著你們跑之類的），玩家不得不在緊張感下衝鋒陷陣，殺敵突破的熱血感在各場戰役做得很不錯。&lt;/p&gt;
&lt;p&gt;此外這代的另一個特色就是軍團集結，例如虎牢關最終階段眾將會集結在關前進行衝鋒；又或是官渡戰曹軍會在袁紹本陣前會合等等。兩方軍團集結相殺，玩家在其中衝鋒陷陣，這種戰場感真的很爽快，這才是在亂軍之中衝殺該有的樣子。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7Fw2AiE.webp&#34;
  alt=&#34;Image&#34;width=&#34;3054&#34; height=&#34;1711&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;br/&gt;▲ 向袁紹本陣衝鋒前的兩軍佈陣，紅色那團就是我們要割的草&lt;/p&gt;
&lt;p&gt;提到戰場事件，就必須和地圖設計做配合，這部份我認為也做得很好。例如十常侍戰在殺入宮中之後利用密道逐步開放地圖，而這些密道又會在後續的刺殺董卓戰用到；又或是關羽千里行的關卡無縫銜接汝南之戰；下邳戰指示玩家從右側偷襲水庫，同時安排呂布在地圖左側襲殺我方製造時間壓力等等，感覺得到有針對戰役事件特別去設計地圖，值得好評。&lt;/p&gt;
&lt;h3 id=&#34;劇情心得&#34;&gt;劇情心得&lt;/h3&gt;
&lt;p&gt;聊完戰鬥體驗跟地圖設計之後，下一個讓我意外的讚賞點其實是劇情。雖然光榮慣例的人人洗白還是少不了，但以無雙系列來說已經算很不錯了。&lt;/p&gt;
&lt;p&gt;這代劇情我印象較深的點有兩段，第一個是劉備終於不只是成天喊著仁義仁義的鬍子據點兵長了，走劉備線會感覺到他的大志、小心思、迷惘，還有妥協跟不甘，比起前幾代立體很多。身為季漢粉，蠻喜歡這一代的詮釋，但因為篇幅只到赤壁結束，劉氏集團能發揮的空間還是有點可惜。&lt;/p&gt;
&lt;p&gt;第二個則是逆天改命的設計，更準確地來說是曹魏線的逆天改命。很喜歡曹魏路線根據典韋跟郭嘉的存活決定赤壁之戰走向的設計。尤其是赤壁戰對「若奉孝在不使孤至此」的郭嘉傳說處理方式，並不是在陣前預知這種把其他軍師當白痴的做法，而是讓郭嘉抱病退休再跟著主角親上戰場而發現，對我來說算蠻加分的。&lt;/p&gt;
&lt;p&gt;不過其實曹魏線原本的路線就很不錯了，尤其是郭嘉病死的雪下飲酒過場足夠唯美。赤壁戰後對華容道的改編也很棒，各種阻攔的伏兵、留下禦敵的友軍，混亂的局部戰場，救援各路將軍之後面對孫劉的最後防線等等。&lt;/p&gt;
&lt;p&gt;其中最棒的兩個關鍵點絕對是曹魏線最終的關羽戰跟張飛戰。在遊戲開場的時候會跟張飛對打作為遊戲教學，而在曹操路線的最後一戰，張飛會在單挑時聊起當初對練的記憶，感覺真的很觸動；而華容道全部清場之後跟曹操一起逃到路口，面對的則是無限覺醒狀態的關羽，一邊對決、一邊提起遊戲開場兩人一起結伴對抗官軍的回憶，這段場景真的做得很不錯。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3iofkkO.webp&#34;
  alt=&#34;Image&#34;width=&#34;3033&#34; height=&#34;1681&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;br/&gt;▲ 華容道底跟曹老闆一起對戰覺醒關羽，本作我最愛的橋段。&lt;/p&gt;
&lt;p&gt;前面提到逆天改命，這代三條逆天改命（也就是ＩＦ劇情）只有曹魏的比較有感，畢竟直接影響了赤壁戰的走向。相比之下孫吳線只有最終劇情有影響，而劉備線更是幾乎感覺不到差別，算是我覺得蠻可惜的地方。&lt;/p&gt;
&lt;p&gt;但救下孫堅的時候還是有點小感動，救典韋跟郭嘉的時候也是如此。也許三國題材的其中一個浪漫就是救下原本會死去的人，像是龐統、孫策，然後想像他們會如何改變歷史吧。&lt;/p&gt;
&lt;p&gt;所以，逆天改命條件還是一個不差全都打了，呵。其中幾個還算讓人蠻有記憶點的啦，例如長坂必須在逃跑路上轉身隻身對抗曹操大軍、對戰超級難打的幻影張角去救孫堅之類的。&lt;/p&gt;
&lt;p&gt;聊完曹操線跟劉備線，可能有些朋友以為接下來要聊聊孫吳線了，但抱歉，沒有。雖然吳國線是唯一一條成功跟白鸞和好，甚至在某種形式上一起共鬥的路線，但我本身就不是很喜歡孫吳，更何況這代的吳國劇情前後互相矛盾的奇怪地方實在太多了，真要寫的話可能就變吐槽了，就輕輕放下吧。&lt;/p&gt;
&lt;h3 id=&#34;好感度系統&#34;&gt;好感度系統&lt;/h3&gt;
&lt;p&gt;無雙起源還有另外一個特色：主角直接被稱作東漢魅魔。而這個評價基本上完全沒錯，這代的好感度系統實在……非常微妙。&lt;/p&gt;
&lt;p&gt;出場的所有武將，注意，是所有武將，都非常親近主角。不管這角色原本戒心多重、身世多慘，心態多扭曲，對主角就是不一樣。在我們主角紫鸞的魅力之下，連類情敵關係的呂布都把主角當成一名對手，搞弱肉強食的董卓都願意讓主角飛翔，實在是東漢友善大使第一人。&lt;/p&gt;
&lt;p&gt;雖然武將劇情跟對話都有點尬，但這部份有關聯到戰場作一些對話，感覺還算是有點誠意。原本以為個人劇情跟戰場對話應該會很有撕裂感，但在第三章以後、各陣營互相戰鬥、開始要你站隊的場合（例如曹劉對立的徐州之戰），敵對武將都會透露出認識你但不得不戰的態度。&lt;/p&gt;
&lt;p&gt;例如選擇劉備陣營，夏侯兄弟會勸你快逃；而選擇曹操陣營的話，關張趙都會帶著敬意和你一戰。尤其是張飛的「什麼都別說了沒關係，讓我們打一架吧」也太帥了吧（這傢伙果然是起源第一女主角吧？）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pcLTabF.webp&#34;
  alt=&#34;Image&#34;width=&#34;3022&#34; height=&#34;1696&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;br/&gt;▲ 第三章會讓玩家選擇路線，路線會影響可攻略對象，果然本作是一款 Galgame&lt;/p&gt;
&lt;p&gt;仔細想想，一些人物塑造的確只能從個人劇情推進。但也因為有個人劇情，這次的張角、董卓、袁紹這些前期人物都塑造的很鮮明，而且不是前幾代那種用各種搞笑標籤來刷第一印象的做法，而是藉由劇情跟戰役過程告訴你張角如何改革失敗、董卓又是怎樣信奉弱肉強食（但是把我跟本陣一起當棄子還是很哭），覺得還算是個不錯的方向。&lt;/p&gt;
&lt;h3 id=&#34;零碎的小地方&#34;&gt;零碎的小地方&lt;/h3&gt;
&lt;p&gt;不知不覺到這段的字數已經過兩千了，最後來聊聊一些零零碎碎的小地方。&lt;/p&gt;
&lt;p&gt;首先就是遊戲的大地圖，一開始其實沒有很看好這種人物超大地圖超小，還要跑到下個城池或戰場的 PRG 跑法，但玩著發現也不錯。尤其是城池和村莊裡 NPC 的諧音梗跟碎碎念，而且隨著各劇本也會有不同的內容。&lt;/p&gt;
&lt;p&gt;例如我一開始有跟袁紹交好，而在官渡之戰袁紹死後，如果路過鄴城會有對話提到「一個個都在曹操曹操的，都忘了袁紹大人了嗎？」就不禁有點感慨，光榮還是很擅長處理這種小地方。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/jlSkIkq.webp&#34;
  alt=&#34;Image&#34;width=&#34;3003&#34; height=&#34;1639&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;br/&gt;▲ 小地圖路過城鎮會觸發ＮＰＣ台詞，路邊小兵都可以臭我們仲家帝，沒在客氣&lt;/p&gt;
&lt;p&gt;除了地圖台詞以外，還有一個功能我懷疑根本是衝著老玩家來的，那就是可以使用前幾代的ＢＧＭ。害得我又跑去重聽了前幾代的行軍、逃亡劇、成都之戰等等，真是經典。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XBChwv0.webp&#34;
  alt=&#34;Image&#34;width=&#34;3015&#34; height=&#34;1687&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;br/&gt;▲ 可以播放前幾代的ＢＧＭ，對老人非常友善的功能&lt;/p&gt;
&lt;p&gt;本作除了主線以外還有許多小戰鬥，例如退治山賊、守守城之類的。而我在打完所有主線之後，就去把小戰鬥都給補一補。其中最後一項地圖任務「夢幻激鬥」，非常推薦把這場戰鬥留到最後，不愧是最終任務，曹孫劉三家聯合對上董卓、張角、袁紹，打起來非常熱血。最後擊殺所有人之後跟呂布單挑並勝利，感覺就是一個完美的句點。&lt;/p&gt;
&lt;p&gt;除了小戰鬥以外，要拿白金還需要補完所有武將的情誼。這代的武將情誼有些都做得很不錯，尤其是貂蟬，在分別許久之後回到村子重逢的部分也很棒，差點讓我一度相信貂蟬才是本作女主角，可惜前面還有朱和跟張飛（？）&lt;/p&gt;
&lt;p&gt;我最後一位交朋友的武將是張角。畢竟是第一章的 Boss，只能在拿到劇情回溯之後再回頭解，乾脆就放到最後跑了，在看到「真是好久不見』反而有種從最後回到最初的感覺。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BmGV8bd.webp&#34;
  alt=&#34;Image&#34;width=&#34;3009&#34; height=&#34;1708&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;br/&gt;▲ 最後達成的繫絆是最初的對手，很喜歡這種前後呼應的調子&lt;/p&gt;
&lt;p&gt;《真三國無雙：起源》要達成白金並不算太困難，雖然有些對手很像是隔壁魂系跑來的（對，就是你呂布，為了拿赤兔不知道死了幾次），後期仍然有找錢幣、刷武器這類要農的要素，但需要重複操作的時長並不多。&lt;/p&gt;
&lt;p&gt;戰鬥的爽快感跟戰場感都很棒，即使只玩戰鬥都沒有問題的程度。劇情有蠻多記憶點，能感覺到這一代不惜砍角重新設計人物的決心，雖然還有略顯粗糙跟不知所云的部份，但還算是處理得不錯了吧。&lt;/p&gt;
&lt;p&gt;這一代畢竟只作到赤壁，雖然一度擔心是不是一款半成品，但起源的確讓我玩得很開心，反而覺得後面還有很多戰役可以做、很多武將可以回歸，漸漸就開始期待下一款作品了，也許這就是無雙重啟後的答卷吧。&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>《河狸浮生記》體驗版遊玩心得</title>
      <link>https://igouist.github.io/post/2025/04/timberborn/</link>
      <pubDate>Thu, 24 Apr 2025 23:40:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/04/timberborn/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/e5D9qHkl.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;136&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在 Steam 城市營造和殖民模擬節購入了這款河狸浮生記，玩了一兩輪覺得挺不錯的，反正這款遊戲還沒有成就系統，也不用像前幾款一樣等白金再整理，決定來順手紀錄一下心得。&lt;/p&gt;
&lt;p&gt;河狸浮生記的主軸是在人類滅亡後的世界裡率領河狸們建造都市，是款偏小品休閒的城市營造遊戲，我個人玩得還蠻開心的，除了河狸蠻可愛的以外，這遊戲的可玩性也符合我對小品城市營造的要求。&lt;/p&gt;
&lt;p&gt;在指揮河狸們建立都市的過程中，我們主要會面臨三個課題：資源管理、災難處理、城市規劃。&lt;/p&gt;
&lt;p&gt;資源管理和大多城市營造遊戲相同，就是在不同階段會有不同的主要資源目標，在生產既有資源的同時，逐步疊加生產新資源的需求。因此需要一步步提升城市蒐集、處理和儲藏資源的能力，同時維持所有資源的生產量和儲藏量，讓城市能夠持續運行。&lt;/p&gt;
&lt;p&gt;而在這款遊戲，因為是河狸當主角，建築大多依賴木頭來製作，初期就需要積極開採木頭拓荒，同時規劃種植樹木來產生長期、持續的木頭收入，畢竟木頭自由就是持續擴張的本錢。&lt;/p&gt;
&lt;p&gt;開伐一定木頭、開始有基本的建築之後，就開始面臨河狸的生存需求，尤其是食物和飲水。這時候就需要採集並種植漿果和胡蘿蔔等食物來源，並在河岸邊採水儲存，才能保證河狸們能夠長期存活。&lt;/p&gt;
&lt;p&gt;在前期階段，擴張跟存活就是我們的主要目標了，吃飽穿暖就是我們河狸村的夢想。&lt;/p&gt;
&lt;p&gt;在第一輪遊戲中，我就大量建造住房、擴展城市，導致河狸數量增長過快，幾天之內就把好幾座倉庫的存糧吃光。但食物大多需要等待種植和收成，過程還會因為遊戲的突發事件中斷食物收入，因此直接陷入一個周轉不靈的斷頭狀態，河狸數量直接從兩百多隻餓死到剩下十幾隻，差點就因為馬爾薩斯陷阱而直接滅村了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bGTzVMHl.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;360&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;▲ 身為一個種田派玩家，從世紀帝國種到河狸村也是當然的&lt;/p&gt;
&lt;p&gt;既然提到了遊戲的「突發事件」，順便就來聊聊災難處理的部份。在遊戲裡，一定週期會發生特殊事件，這些事件都會對村莊造成一定壓力。&lt;/p&gt;
&lt;p&gt;例如乾旱會讓所有水源好幾天不出水，而因為農田等種植作物會需要灌溉（附近有水）的土地才能正常生長，缺水的狀況下作物會枯萎死亡；同時遊戲的主要動力來源是水車，因此水流中斷、又沒有靠風力和人力發電補上缺口的情況下，工業設施也會陷入停擺，可以想像成缺糧又缺電的狀況。&lt;/p&gt;
&lt;p&gt;另一種災害則是惡水潮，是讓所有水源一定時間內只產生惡水。&lt;/p&gt;
&lt;p&gt;惡水就是汙染水，在遊戲中很明確能看見紅色水流，基本上會導致作物汙染死去、河狸中毒等等（設定上是已滅亡的人類搞出來的，謝嚕），但惡水也是產生炸藥的重要資源，只能說有好有壞。&lt;/p&gt;
&lt;p&gt;前面提到的乾旱只要利用建造水庫、調整河床增加村莊河流的蓄水能力等手段就可以處理。但惡水潮因為是原本產生清水的地方變成產生污染的惡水，因此原本流經村莊的救命河流直接變成毒死所有作物的毒水。&lt;/p&gt;
&lt;p&gt;這時候，河流蓄水能力越好，越具有能抵抗乾旱的特性（河床較深、河道蜿蜒等等），惡水反而就留得越久。因此惡水潮的處理上比起乾旱更加麻煩，幾乎一定要用調整河道走向和閘門的方式來引流，確保災害時期的水流能確實轉向，遠離我們的村莊，否則只能等著惡水退去之後摘除受汙染的作物，直接認賠。&lt;/p&gt;
&lt;p&gt;就跟避免缺人所以收一堆人，而且還沒有淘汰機制的某些公司一樣，咳咳咳。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/l31UlADl.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;360&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;▲ 惡水潮讓水源變成受到汙染的紅色惡水，必須建立水壩阻擋並引流&lt;/p&gt;
&lt;p&gt;以上災害雖然在前期發展階段時都會帶來痛苦和打擊，但確實提升的遊戲的可玩性，讓玩家會為了保護河狸、處理災害而調整村莊的設計，提高村莊的強韌程度。&lt;/p&gt;
&lt;p&gt;到了中盤，確保擴張跟生存能力，也能稍微撐過災害時期之後，就會逐漸進入重構優化時期。&lt;/p&gt;
&lt;p&gt;因為衣食無缺、有吃有住之後，差不多也有建造水壩、炸藥的餘裕了，這階段就可以開始嘗試改變水流方向、設計河流跟水的走向，像是下挖河床的深度，或是建造水庫來留住更多水資源，讓城市能安穩度過突發事件的缺水時期。&lt;/p&gt;
&lt;p&gt;如果更有餘裕的話，也可以開始發展娛樂、裝飾品，一些進階的餐點，藉此提升河狸的心情指數了（不過我走一個鐵腕路線，放幾張醫療床給河狸躺已經很好，下班還有舞廳可以跳個舞就差不多了）&lt;/p&gt;
&lt;p&gt;這階段我主要都在玩遊戲的動能系統，可以想像成拉電線遊戲。遊戲的工廠型建築需要動能，而動能需要產生跟運輸，像是利用水車和風車產生動能，再使用管道運輸動能提供給工廠。&lt;/p&gt;
&lt;p&gt;前期容易在城市中心就近放個工廠處理木板，先求一個能動就好。但隨著城鎮擴張，這些散落的動能建築就會漸漸變成「技術債」，不方便繼續加蓋或串接其他建築來規劃新的生產路線，這時候就不得不重新設計、調整動能運輸的建築和路線，做出發電區、工廠區等等，來讓木板、紙張等進階資源的製作工廠漸漸規模化。&lt;/p&gt;
&lt;p&gt;這段時間更多是在調整和修正初期的城市規劃，在村莊裡劃設儲藏區、住宅區、工業區、各種道路等等，想辦法藉由各種調整和重新設計來讓整個城市運作的效率往上提升。&lt;/p&gt;
&lt;p&gt;並且因為遊戲中的部份建築是可以疊的，例如民房上面可以疊民房，倉庫上面可以疊倉庫，因此路線規劃（尤其是樓梯）就會相對重要。如果初期建設做得很平面、有個空地就丟棟建築，而沒有先做簡單的分區規劃的話，中期建設就會花更多時間在重新設計建築和動線。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pqPrLoal.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;361&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;▲ 遊戲的建築可以疊放，但我這個人實在沒什麼創意，所以河狸看起來都像住在貨櫃屋&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更新：&lt;a href=&#34;https://store.steampowered.com/news/app/1062090/view/544479673001706540?l=tchinese&#34;&gt;五月的更新&lt;/a&gt;追加了空中纜車和建築上面能疊放地形，立體建築能玩的地方又更多了，舒服&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;到這個階段，災害事件基本上都有應變方式了，村莊也優化跟擴張得差不多，遊戲也進入放置時期，漸漸會考慮開下一把來玩了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;聊完了一兩輪遊戲心得，接著就紀錄一些小缺點吧，首先就是前面提到的災害事件。目前的災害事件只有兩種：乾旱跟惡水潮，中盤把水流處理好之後，節奏就變得有點單調。&lt;/p&gt;
&lt;p&gt;出事我就開關閘門、操作一下水流，沒事就歌舞昇平（這篇心得也是一邊看著河狸們游泳一邊寫的），漸漸變得只剩作業感了。還是希望之後能加入更多種類的災害跟發生頻率，不然跟我上班看到告警就按著 SOP 做事有什麼兩樣囧&lt;/p&gt;
&lt;p&gt;另外一項是我個人感覺小品城市建造都會有的：遊戲中後期左右，新村莊都擴得差不多之後，就不再有大型目標了，基本上就是局部優化，像是調整水流、疊建築、搞電線這些細節處理。&lt;/p&gt;
&lt;p&gt;如果沒有什麼特別要解決的問題，就會顯得無聊，遊戲和工作都是這樣。到了這階段不如新開一把。可能這就跟新創賭徒發現公司擴張停滯後就想開新公司一樣吧（？）&lt;/p&gt;
&lt;p&gt;不過遊戲中提供的兩種河狸部落各有特色（以木頭為主的河狸，跟以鍛鐵為主的，呃，還是河狸）搭配地形還是有一定可玩性。但可惜我是個體驗俠，都摸過一輪就該玩下一款等更新囉，畢竟收藏庫還這麼多款在排，也不是每款遊戲都能像 Rimworld 能掛著當柬埔寨模擬器跟八點檔產生器嘛。&lt;/p&gt;
&lt;p&gt;考慮到這遊戲還在搶先體驗版階段，還是期待以後的發展啦。至少不要像之前玩過的某幾款直接死掉就可以了（對，就是你《石爐》。明明自訂建築這個點很棒呀！）&lt;/p&gt;
&lt;p&gt;只能說，河狸們，願您的國長治久安。我就坐等正式推出吧。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更新：河狸浮生記將在 2026/3 推出 1.0 版本，已經準備好來趟木頭龐克之旅了🪵&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/uGusQjkmlRg?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;最後聊點題外話：玩這類城市營建遊戲的時候，常有種跟程式開發等既有概念串起來的感覺？&lt;/p&gt;
&lt;p&gt;例如前面提到的城市規劃：初期沒有做分區，建築又亂疊，中期就要花更多時間規劃和重建……&lt;/p&gt;
&lt;p&gt;又或者是「擴張、生存、彈性」的產品節奏：先蒐集資源、積極擴張、想辦法活下來，基本需求都穩定之後，就開始著手進行優化，提升整體效率和面對突發狀況時的容錯率等等……&lt;/p&gt;
&lt;p&gt;感覺很多地方可以關聯起來，不過也有可能是我比較常想東想西就是了。&lt;/p&gt;
&lt;p&gt;畢竟我玩 Shadow Tactics 系列也覺得「哦這跟解題和修 Bug 好像」，玩 4X（探索、擴張、開發、征服）也覺得「哦這跟學習程式技能好像」，也許它們都只是剛好符合我的某些思考框架而已吧？&lt;/p&gt;
&lt;p&gt;大概這樣，原本只是想說寫點東西，結果比預期的還要多。難怪老前輩的簽名檔是「寫文紓壓」，可能真有幾分道理。那麼，我們下篇見～&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>JMeter: 使用 Constant Throughput Timer 設置固定吞吐量</title>
      <link>https://igouist.github.io/post/2025/02/jmeter-constant-throughput-timer/</link>
      <pubDate>Sat, 01 Feb 2025 08:15:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/02/jmeter-constant-throughput-timer/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XGOmHat.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;334&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們在之前 &lt;a href=&#34;https://igouist.github.io/post/2022/10/jmeter&#34;&gt;JMeter&lt;/a&gt; 的筆記時，介紹過直接對服務爆打一波的作法：簡單來說就是打好幾輪看看服務撐不撐得住，然後紀錄能吃的最大數量，可以說是非常暴力的做法。&lt;/p&gt;
&lt;p&gt;但如果我們想要模擬某個固定數值的請求量（例如每分鐘 100 次請求）、又或是需要用固定速率打出去（例如每 5 秒得敲一下），就不能像上次一樣粗暴地全力出擊了。&lt;/p&gt;
&lt;p&gt;這時候，&lt;strong&gt;我們就可以用 JMeter 提供的 Constant Throughput Timer（固定吞吐量計時器）來限制呼叫頻率&lt;/strong&gt;，馬上來筆記一篇。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;以下範例會使用到 JMeter 的 HTTP Request 等功能，還不太瞭解的朋友推薦先閱讀上一篇筆記：&lt;a href=&#34;https://igouist.github.io/post/2022/10/jmeter&#34;&gt;使用 JMeter 來對 API 壓力測試吧&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;先確認一下環境：我們有一組腳本，內容只是簡單地戳一下 API&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/O3yDOx1.webp&#34;
  alt=&#34;Image&#34;width=&#34;1888&#34; height=&#34;1062&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我們希望可以模擬線上使用者在尖峰時段的使用頻率，並確認我們服務挺得過去，所以需要把這組腳本調整成：&lt;strong&gt;「每分鐘戳 20 次，持續五分鐘」&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：有時候我們收到的需求會是「持續五分鐘，然後總量為 100」這種，意思是差不多的，反正先確認好 &lt;strong&gt;「每分鐘的數量 x 持續幾分鐘 = 總量」&lt;/strong&gt; 這幾個關鍵數字再說嚕。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先讓我們加入一組 Constant Throughput Timer：



&lt;img
  src=&#34;https://image.igouist.net/ymK5ZCq.webp&#34;
  alt=&#34;Image&#34;width=&#34;1669&#34; height=&#34;844&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且找到 Constant Throughput Timer 裡面的 &lt;strong&gt;Target Throughput（每分鐘呼叫次數）&lt;/strong&gt;，我們這次的需求是每分鐘要戳他個 20 次，這邊就直接放 20.0：



&lt;img
  src=&#34;https://image.igouist.net/I29PVrK.webp&#34;
  alt=&#34;Image&#34;width=&#34;969&#34; height=&#34;441&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;圖片可以看到還有另一個選項「Calculate Throughput based on」，這個我們晚點再介紹，先維持在「this thread only」&lt;/p&gt;
&lt;p&gt;接著回到 Thread Group，因為我們這次的場景是用時間來計算的，所以需要&lt;strong&gt;把底下的 Specify Thread Lifetime 打開，並根據要執行的時間來填上秒數&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因為我們的需求是跑 5 分鐘，所以先丟個 300 秒：



&lt;img
  src=&#34;https://image.igouist.net/0eam5FB.webp&#34;
  alt=&#34;Image&#34;width=&#34;1741&#34; height=&#34;1163&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而且我們這次是用執行時間計算數量的，不希望 Loop Count 影響到我們，直接甩個 Infinite 給他（對，我們就是模擬有個使用者坐在那邊無聊一直按按鈕，而且他節奏感還很好）&lt;/p&gt;
&lt;p&gt;執行之後就可以確認總量跟每分鐘的吞吐量有沒有符合我們的設置囉：



&lt;img
  src=&#34;https://image.igouist.net/h98Pdvd.webp&#34;
  alt=&#34;Image&#34;width=&#34;2698&#34; height=&#34;618&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;很好！現在我們已經會使用 Constant Throughput Timer 來控制壓力測試時的吞吐量了。&lt;/p&gt;
&lt;p&gt;有興趣的朋友可以回去調整各個參數觀察看看，也可以改個 Thread Group 的 Thread 數量看看結果差異，例如當我把上面範例的 Thread 改成 2：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1iY0YdJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;2684&#34; height=&#34;547&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見數量跟吞吐量都有影響（算總數寫報告的時候記得別算錯了 xD）&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;接著讓我們認識一下 Constant Throughput Timer 裡面的 &lt;strong&gt;Calculate Throughput based on&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有時候我們只在乎每分鐘要打多少筆，不是很在乎每個 Threads 實際分配的量（或是不想算數學），這時候就可以調整 Constant Throughput Timer 的模式&lt;/p&gt;
&lt;p&gt;像是把模式改成「All Active Threads」，就能直接訂最終數字（例如每分鐘就是要打到 2000 筆），剩下就讓 JMeter 自己去分配，可說是懶人好幫手&lt;/p&gt;
&lt;p&gt;Constant Throughput Timer 的 Calculate Throughput based on 總共有五個選項：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;模式&lt;/th&gt;
          &lt;th&gt;說明&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;This Thread Only&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;每個 Thread 各自獨立設定目標吞吐量&lt;/strong&gt;&lt;br/&gt;例如設定 20/min、開 5 個 Thread → 實際總量 100/min&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;All Active Threads&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;目標吞吐量由所有 Thread Group 的所有 Thread 共同分攤&lt;/strong&gt;&lt;br/&gt;例如設定 20/min、開 5 個 Thread → 每 Thread 各約 4/min，總量仍是 20/min&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;All Active Threads&lt;br/&gt; in Current Thread Group&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;同上，但只抓&lt;strong&gt;當前 Thread Group 的 Thread&lt;/strong&gt; 來分攤&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;All Active Threads (Shared)&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;和 All Active Threads 一樣意思，但&lt;strong&gt;所有 Thread 共用同一個計時器&lt;/strong&gt;，請求分配會更均勻&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;All Active Threadss&lt;br/&gt; in Current Thread Group (Shared)&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;基本上就是上面兩個加起來：當前 Thread Group 的所有 Thread 共用同一個計時器&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以再根據壓測的場景決定要選擇哪一個模式，供參考。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最後整理一下 Constant Throughput Timer 的適用場景，有以下狀況的時候就可以考慮使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模擬某個固定數值的請求量（例如每分鐘Ｘ次）&lt;/li&gt;
&lt;li&gt;需要穩定地以固定速率發送請求（不能一開頭就重拳出擊）&lt;/li&gt;
&lt;li&gt;老闆說：「我們這個測試不能更貼近真實使用者一點嗎？」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;各位朋友可以再動手玩玩看。我們下次見～&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.browserstack.com/guide/jmeter-constant-throughput-timer&#34;&gt;JMeter Constant Throughput Timer : Tutorial | BrowserStack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.blazemeter.com/blog/jmeter-throughput&#34;&gt;How to Use JMeter&amp;rsquo;s Throughput Timer - blazemeter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.codekru.com/jmeter/constant-throughput-timer-in-jmeter-with-examples&#34;&gt;Constant Throughput Timer in Jmeter with examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/m0_38039437/article/details/104115312&#34;&gt;Jmeter 吞吐量定时器 Constant Throughput Timer (CSDN)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/hpzyang/p/13781788.html&#34;&gt;jmeter 固定吞吐量控制器 Constant Throughput Timer - 博客園&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;還有最實際的：



&lt;img
  src=&#34;https://image.igouist.net/UttrtAL.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;445&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>WakaTime: 隱藏敏感資訊、開啟離線暫存、卡在 Initialized 時的參考解法</title>
      <link>https://igouist.github.io/post/2025/01/wakatime-hide-project-name-and-offline-and-initialized/</link>
      <pubDate>Sun, 12 Jan 2025 00:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2025/01/wakatime-hide-project-name-and-offline-and-initialized/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5UBS6ab.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;410&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;WakaTime 是一款紀錄 Coding 時間的好用工具，只要安裝 IDE 的擴充套件，就可以輕鬆地記錄各個語言、編輯器、專案所用的時間。我從 2020 年寫了篇 &lt;a href=&#34;https://igouist.github.io/post/2020/06/wakatime/&#34;&gt;WakaTime 介紹文章&lt;/a&gt; 開始，也一路用到現在 2025 了，已經習慣動不動上去看兩眼的生活。&lt;/p&gt;
&lt;p&gt;但在相對封閉的一些環境進行開發時，我們可能需要先對 WakaTime 調整一些設定才能用得安心，這篇筆記就分享一下最近遇到的三個場景，供有興趣的朋友參考參考。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#在-wakatime-隱藏敏感資訊檔案名稱專案名稱&#34;&gt;在 WakaTime 隱藏敏感資訊（檔案名稱、專案名稱…）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#開啟-wakatime-離線暫存offline&#34;&gt;開啟 WakaTime 離線暫存（Offline）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#wakatime-卡在初始化initialized試試下載-wakatime-cli&#34;&gt;WakaTime 卡在初始化（initialized）？試試下載 WakaTime-CLI&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;在-wakatime-隱藏敏感資訊檔案名稱專案名稱&#34;&gt;在 WakaTime 隱藏敏感資訊（檔案名稱、專案名稱…）&lt;/h2&gt;
&lt;p&gt;Wakatime 除了會記錄使用的程式語言和 IDE 等工具以外，也會紀錄專案、檔案路徑等等。但有時候我們可能並不希望記錄這些資訊（像是業主委託的專案名稱有點敏感啦、路徑可能有個人姓名之類的），這時就需要調整 Wakatime 的 Config，來把相關的資訊隱藏起來。&lt;/p&gt;
&lt;p&gt;Wakatime 的設定會放在 &lt;code&gt;.wakatime.cfg&lt;/code&gt; &lt;br/&gt;（Windows 預設會在 &lt;code&gt;C:\Users\{User}\&lt;/code&gt; 或 &lt;code&gt;C:\Users\{User}\.wakatime&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;開啟之後就會看見自己的 API Key 等設定，像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;settings&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;api_key&lt;/span&gt; = &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;***&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;能在 &lt;code&gt;.wakatime.cfg&lt;/code&gt; 調整的設定值可以參考官方文檔：&lt;a href=&#34;https://github.com/wakatime/wakatime-cli/blob/develop/USAGE.md&#34;&gt;USAGE.md&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;因為這次我們的目標是隱藏資訊，所以會用到的主要是 Hide 四兄弟：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hide_file_names&lt;/code&gt;：隱藏檔案名稱&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hide_branch_names&lt;/code&gt;：隱藏分支名稱&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hide_project_names&lt;/code&gt;：隱藏專案名稱&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hide_project_folder&lt;/code&gt;：隱藏專案資料夾&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全打開的話會像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;settings&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;api_key&lt;/span&gt; = &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;***&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;hide_file_names&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;hide_branch_names&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;hide_project_names&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;hide_project_folder&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下來讓我們簡單認識一下它們。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;開啟 &lt;code&gt;hide_file_names&lt;/code&gt;（隱藏檔案名稱）之後，檔案名稱就不會傳送上去。&lt;/p&gt;
&lt;p&gt;在 WakaTime 看見的會是 HIDDEN：



&lt;img
  src=&#34;https://image.igouist.net/xbKTWlCm.webp&#34;
  alt=&#34;Image&#34;width=&#34;320&#34; height=&#34;114&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：因為檔案名稱變成 HIDDEN 了，所以如果原本有用自訂規則來偵測副檔名之類的做法（像 &lt;code&gt;.linq&lt;/code&gt; =&amp;gt; C#）就會吃不到囉。如果有使用 Custom rules 的朋友請再注意一下。&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;開啟 &lt;code&gt;hide_project_names&lt;/code&gt;（隱藏專案名稱）之後，則會幫你的專案取一個新名字。這個名字會在專案資料夾底下建一個 &lt;code&gt;.wakatime-project&lt;/code&gt; 的檔案並存放在裡面。&lt;/p&gt;
&lt;p&gt;在 WakaTime 看見的就會是這組專案名稱囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vwtakngm.webp&#34;
  alt=&#34;Image&#34;width=&#34;320&#34; height=&#34;168&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最後這組 &lt;code&gt;hide_project_folder&lt;/code&gt; 則是會隱藏完整路徑。&lt;/p&gt;
&lt;p&gt;原本的路徑像是這樣的話：&lt;code&gt;/User/me/projects/bar/src/file.ts&lt;/code&gt; &lt;br/&gt;
就會變成這樣：&lt;code&gt;src/file.ts&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果路徑上有個人資訊的時候，像是 &lt;code&gt;C:\Users\{User}\...\file.ts&lt;/code&gt; 的話，就很適合把 &lt;code&gt;hide_project_folder&lt;/code&gt; 打開。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;除了上面四個 &lt;code&gt;hide&lt;/code&gt; 兄弟以外，另一個隱藏資訊常用的設定是 &lt;code&gt;hostname&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;推薦在電腦名稱需要隱藏的時候使用，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;settings&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;hostname&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;nice_machine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 WakaTime 就可以看見自訂的機器名稱囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/R9T4TfTm.webp&#34;
  alt=&#34;Image&#34;width=&#34;320&#34; height=&#34;131&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;開啟-wakatime-離線暫存offline&#34;&gt;開啟 WakaTime 離線暫存（Offline）&lt;/h2&gt;
&lt;p&gt;除了隱藏專案相關的敏感資訊以外，有時候我們也會到一些暫時無法聯網的地方工作。&lt;/p&gt;
&lt;p&gt;這時候像 Git 這種可以離線工作、連線再上傳的工具就非常方便。&lt;/p&gt;
&lt;p&gt;而 WakaTime 同樣也有 Offline 模式，開啟之後即使是離線的，也會把相關的紀錄暫存起來，等到有網路的時候再上傳。&lt;/p&gt;
&lt;p&gt;要開啟的話，同樣要先到上面提到的 &lt;code&gt;.wakatime.cfg&lt;/code&gt;，加上：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;offline&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣當我們沒有接上網路的時候，WakaTime 就會顯示離線模式：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CfQU139.webp&#34;
  alt=&#34;Image&#34;width=&#34;1121&#34; height=&#34;137&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;離線模式時，會將資料暫存在 &lt;code&gt;.wakatime/offline_heartbeats.bdb&lt;/code&gt;，等待重新連線後再上傳&lt;/p&gt;
&lt;p&gt;這樣就可以安心繼續 Coding 囉～&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果想手動上傳的話，也可以找到 &lt;code&gt;.wakatime/&lt;/code&gt; 底下的 Wakatime CLI，直接呼叫上傳離線資料的指令&lt;/p&gt;
&lt;p&gt;以我在 Windows 64 位元的工作機為例，應該會找到 &lt;code&gt;wakatime-cli-windows-amd64.exe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;接著就可以使用 &lt;code&gt;--sync-offline-activity&lt;/code&gt; 來同步資料：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 確認待上傳的離線資料筆數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.\wakatime-cli-windows-amd64.exe --offline-count
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 上傳離線資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.\wakatime-cli-windows-amd64.exe --sync-offline-activity &lt;span style=&#34;color:#ae81ff&#34;&gt;9999&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/akMO4XA.webp&#34;
  alt=&#34;Image&#34;width=&#34;930&#34; height=&#34;276&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;wakatime-卡在初始化initialized試試下載-wakatime-cli&#34;&gt;WakaTime 卡在初始化（initialized）？試試下載 WakaTime-CLI&lt;/h2&gt;
&lt;p&gt;這次還有遇到一個怪怪的狀況：WakaTime 的狀態一直卡在初始化（initialized）&lt;/p&gt;
&lt;p&gt;折磨半天之後，終於找到了問題：我的開發環境連不到 Github，而 &lt;strong&gt;WakaTime 會嘗試上去 Github 更新 WakaTime CLI&lt;/strong&gt;，於是就卡住了。&lt;/p&gt;
&lt;p&gt;這時候可以嘗試手動下載 WakaTime CLI 來解決。首先到 &lt;a href=&#34;https://github.com/wakatime/wakatime-cli/releases&#34;&gt;github/wakaTime-cli - Release&lt;/a&gt;，根據作業系統下載對應的壓縮檔（我用另一台電腦上去拉的）&lt;/p&gt;
&lt;p&gt;解壓縮之後會得到 &lt;code&gt;wakatime-cli-{os}-{version}.exe&lt;/code&gt;，以我在 Windows 64 位元的工作機為例，就會是 &lt;code&gt;wakatime-cli-windows-amd64.exe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;把這個 &lt;code&gt;exe&lt;/code&gt; 手動丟到 &lt;code&gt;.wakatime&lt;/code&gt; 資料夾裡，就可以重開 IDE 觀察看看有沒有疏通囉～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：因為初始化的步驟不僅這項，也可能還有其他原因導致卡住&lt;br/&gt;如果有用其他解法成功打通的朋友也歡迎分享～&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/wakatime/wakatime-cli/blob/develop/USAGE.md&#34;&gt;USAGE.md - wakatime/wakatime-cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/wakatime/vscode-wakatime/issues/156&#34;&gt;Wakatime stuck at VS Code initializing #156 - wakatime/vscode-wakatime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞出門玩：2024 韓國釜山</title>
      <link>https://igouist.github.io/post/2024/11/2024-busan-travel/</link>
      <pubDate>Fri, 22 Nov 2024 19:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/11/2024-busan-travel/</guid>
      <description>&lt;p&gt;今年難得有機會出國逛逛（雖然是蹭女友公司的員工旅遊，謝謝隔壁老闆！），想說 2024 也要結束了，決定趁著放長假的時候來整理一下。&lt;/p&gt;
&lt;p&gt;因為是第一次嘗試發旅遊文，打算順著路上景點丟丟照片、簡單寫一下心得就好。如果之後有繼續發同系列的文章再來慢慢微調吧。&lt;del&gt;說不定就趁勢轉型成旅遊美食部落格了呢&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;這篇主要會分成三個部份：行前準備、景點紀錄、個人心得。那麼就開始囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MAlzY5ql.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 海東龍宮寺的燈，放這張當封面只是因為我喜歡。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#行前準備&#34;&gt;行前準備&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#景點紀錄&#34;&gt;景點紀錄&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#密陽豬肉湯飯&#34;&gt;密陽豬肉湯飯&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#富平罐頭市場&#34;&gt;富平罐頭市場&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#青沙浦膠囊列車天空步道&#34;&gt;青沙浦（膠囊列車、天空步道）&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#購物商場沙灘&#34;&gt;購物商場、沙灘&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#甘川洞&#34;&gt;甘川洞&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#南浦洞&#34;&gt;南浦洞&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#李夏亭醬蟹&#34;&gt;李夏亭醬蟹&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#海東龍宮寺&#34;&gt;海東龍宮寺&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#老奶奶伽倻小麥冷麵&#34;&gt;老奶奶伽倻小麥冷麵&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;行前準備&#34;&gt;行前準備&lt;/h2&gt;
&lt;p&gt;這趟除了出門必須的整頓行李和旅平險以外，還另外買了有交通卡和換匯功能的 &lt;a href=&#34;https://www.kkday.com/zh-tw/product/18161-t-money-public-transit-card-pick-up-at-taiwan-taoyuan-international-airport-south-korea&#34;&gt;WOWPass&lt;/a&gt;、提供手機網路的 &lt;a href=&#34;https://www.kkday.com/zh-tw/product/132542-korea-4g-high-speed-esim&#34;&gt;SKT eSIM&lt;/a&gt;，這兩個在 KKday 購買的話可以選擇直接在機場取貨。&lt;/p&gt;
&lt;p&gt;但我們交通卡跟 eSIM 取貨的航廈選錯，導致我們要在桃園機場兩個航廈之間來回跑囧，如果有一樣蠢的朋友，提醒一下桃園機場有&lt;a href=&#34;https://www.youtube.com/watch?v=9auPt3DqNWg&#34;&gt;接駁電車&lt;/a&gt;可以往來第一航廈跟第二航廈…&lt;/p&gt;
&lt;p&gt;此外要特別注意的應該是地圖 APP 了。&lt;/p&gt;
&lt;p&gt;由於韓國的法規限制（主要是因為跟北韓的尷尬狀況），較精密的地理資訊只能放在國內的伺服器。這也導致 Google Map 在韓國……不是那麼好用。&lt;/p&gt;
&lt;p&gt;因此要前往韓國之前，通常會在 &lt;a href=&#34;https://map.naver.com/&#34;&gt;Naver Map&lt;/a&gt; 和 &lt;a href=&#34;https://m.map.kakao.com/&#34;&gt;Kakao Map&lt;/a&gt; 裡面選一個用&lt;/p&gt;
&lt;p&gt;我們這趟用的是 Naver Map，主要是因為 APP 裡至少還有簡中能選。&lt;br/&gt;&lt;del&gt;我們這些英文不好的孩子是這樣的&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;延伸閱讀：&lt;a href=&#34;https://exittaiwan.com/posts/%E7%82%BA%E4%BB%80%E9%BA%BC-google-maps-%E5%9C%A8%E9%9F%93%E5%9C%8B%E4%B8%8D%E8%A1%8C%E7%94%A8%E6%9C%89%E4%BB%80%E9%BA%BC%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88/&#34;&gt;為什麼 Google Maps 在韓國不行用？有什麼替代方案？ - ExitTaiwan&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;景點紀錄&#34;&gt;景點紀錄&lt;/h2&gt;
&lt;p&gt;接下來就是這幾天去的景點了。&lt;del&gt;終於可以開始曬照片了&lt;/del&gt;&lt;/p&gt;
&lt;h3 id=&#34;密陽豬肉湯飯&#34;&gt;密陽豬肉湯飯&lt;/h3&gt;
&lt;p&gt;下飛機後第一餐是豬肉湯飯，湯送上來的時候還在滾，對怕燙的朋友不太友善&lt;/p&gt;
&lt;p&gt;湯頭還不錯，但味道比想像的還清，主要靠旁邊的調味料（蝦米）自行調味&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fb5vm43l.webp&#34;
  alt=&#34;Image&#34;width=&#34;351&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在韓國用餐真的是必備各式小菜，從這餐開始的每一餐都有小菜，是真的每一餐，完全想不起來有哪頓飯是沒有泡菜吃的，很切實地感受到他們對小菜的熱愛。&lt;/p&gt;
&lt;p&gt;另外還得到了來自導遊的重要情報(?)：在韓國，&lt;strong&gt;菜單上東西越少的店就越好吃&lt;/strong&gt;，其他那些菜單列一大堆的店都是來騙觀光客的。真實性有待商榷，但值得參考 XD&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;後來回台灣之後發現常看的 YT 在家做生魚片&lt;br/&gt;也來過這家豬肉湯飯，有興趣的朋友也可以參考影片（約 0:59 處）：&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/JGpY33CqKaw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;富平罐頭市場&#34;&gt;富平罐頭市場&lt;/h3&gt;
&lt;p&gt;接著前往罐頭市場，據說是因為韓戰時期在這裡進行了大量軍用品（尤其是罐頭）的交易而得名。（&lt;a href=&#34;https://big5chinese.visitkorea.or.kr/svc/whereToGo/locIntrdn/rgnContentsView.do?vcontsId=73590&#34;&gt;富平市場(罐頭市場) - 韓國觀光公社&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;我們抵達的時候已經有點晚了，基本上只有十字路上的攤販。挑了一攤酥炸豬排，味道意外地不錯，口感也紮實。&lt;/p&gt;
&lt;p&gt;另外在導遊的鼓吹下還買了韓國香瓜。但實在沒什麼味道，就…脆脆的，我個人還是比較喜歡台灣的水果啦，甜的好。&lt;/p&gt;
&lt;p&gt;延伸閱讀：&lt;a href=&#34;https://www.busaniris.com/korean-melon/&#34;&gt;韓國香瓜 참외 要這樣吃才道地 - 韓國x愛夢瑞絲&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/q3nj1Ful.webp&#34;
  alt=&#34;Image&#34;width=&#34;481&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 西瓜汁，要 12000 韓元（約 300 新台幣），讓我確實認識到在國外買東西一定要先問價格&lt;/p&gt;
&lt;h3 id=&#34;青沙浦膠囊列車天空步道&#34;&gt;青沙浦（膠囊列車、天空步道）&lt;/h3&gt;
&lt;p&gt;隔天是一些觀光行程，但可能我比較貪吃，對這部份的行程相對無感一點。總感覺天空步道只是凸出去海上的平台、膠囊列車就是看海曬太陽的小包廂。&lt;/p&gt;
&lt;p&gt;但管他呢，辦公室關久了，看看海心情也很不錯。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3EzI0dUl.webp&#34;
  alt=&#34;Image&#34;width=&#34;481&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 膠囊列車中間的桌子感覺很適合放筆電，光照也充足，抬頭還能看海，很適合加班。&lt;/p&gt;
&lt;h3 id=&#34;購物商場沙灘&#34;&gt;購物商場、沙灘&lt;/h3&gt;
&lt;p&gt;大家在瞎拼的時候我們這組在忙著買零食。好ㄘ。&lt;/p&gt;
&lt;p&gt;有個印象蠻深的小地方是：韓國商場裡各店家的門口都會有個飲料架，讓遊客可以先把飲料放著再入店。在台灣似乎沒看過，但我個人是不太敢放（查了一下還真的有相關&lt;a href=&#34;https://tw.news.yahoo.com/%E9%A6%96%E7%88%BE%E6%98%8E%E6%B4%9E-%E9%A3%B2%E6%96%99%E6%94%BE%E7%BD%AE%E5%8D%80-%E5%8F%B0%E9%81%8A%E5%AE%A2%E7%9B%AE%E6%93%8A-%E7%8F%BE%E5%A0%B4%E7%89%B9%E8%AA%BF-%E5%9A%87-103100516.html&#34;&gt;新聞&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;另外，可能因為美妝盛行，在試戴帽子會提供衛生紙來墊額頭。雖然都是百貨商場，但發現這些小地方的差異也蠻有趣的。&lt;/p&gt;
&lt;p&gt;買完之後的行程是沙灘散步，城市緊接著沙灘的感覺還蠻不錯，辦公大樓和大馬路旁邊就是沙灘和街頭藝人，也沒什麼衝突感。傍晚時活動的人數也比想像中多很多，到處都是跑步遛狗的人類。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/J6DW8Qwl.webp&#34;
  alt=&#34;Image&#34;width=&#34;481&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 我們前往時剛好是沙灘季前幾天，有沙雕，運氣不錯。&lt;/p&gt;
&lt;h3 id=&#34;甘川洞&#34;&gt;甘川洞&lt;/h3&gt;
&lt;p&gt;在山坡上的彩繪村。劇說是韓戰時期由難民組成的村莊，現在感覺是遊客眾多的拍照聖地。&lt;/p&gt;
&lt;p&gt;有小王子的雕塑，也有韓服租借的服務，但排隊的人都超級多。另外還有非常多、非常多的咖啡廳及紀念品店。身為喜歡小廢物的朋友，買得還算開心。&lt;/p&gt;
&lt;p&gt;延伸閱讀：&lt;a href=&#34;https://couplehuang.com/gamcheon_culture_village/&#34;&gt;甘川洞文化村 감천문화마을：告別悲情，重獲新生的釜山繽紛山城 - 艸頭黃這一家&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aUmCPdwl.webp&#34;
  alt=&#34;Image&#34;width=&#34;560&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 無法排隊的老人，在咖啡廳悠閒地等待集合時間。&lt;/p&gt;
&lt;h3 id=&#34;南浦洞&#34;&gt;南浦洞&lt;/h3&gt;
&lt;p&gt;南浦洞給我的感覺……有點像西門町或新堀江？我們這趟主要是在 BIFF 廣場附近閒逛，這區域是由一堆攤販組成的觀光街，附近還有美妝店，逛起來特別有熟悉感。&lt;/p&gt;
&lt;p&gt;可以參考街景圖會更有感：&lt;/p&gt;
&lt;iframe src=&#34;https://www.google.com/maps/embed?pb=!4v1732268057812!6m8!1m7!1savVMDd45WJ2vNXvagkoDMg!2m2!1d35.09885051691987!2d129.0289984970694!3f52.77219668100617!4f-2.850030680253468!5f0.7820865974627469&#34; width=&#34;600&#34; height=&#34;450&#34; style=&#34;border:0;&#34; allowfullscreen=&#34;&#34; loading=&#34;lazy&#34; referrerpolicy=&#34;no-referrer-when-downgrade&#34;&gt;&lt;/iframe&gt;
&lt;br/&gt;
&lt;p&gt;吃了幾攤，比較特別的應該是&lt;a href=&#34;https://maps.app.goo.gl/XtfWpWBFeKyV7FBQ7&#34;&gt;糖餅&lt;/a&gt;，吃起來像是甜的燒餅裡面塞堅果，還算不錯。&lt;/p&gt;
&lt;p&gt;另外還去吃了道地的炸醬麵，結果發現我更喜歡台式的韓國炸醬麵，雖然洋蔥跟醬是蠻順口的啦，但跟想像的不太一樣。反而糖醋肉吃起來挺不錯的，肉咬起來也是挺緊實緊實。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/kvaUQfil.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;481&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 糖醋肉，還有形狀相當微妙的醬料。&lt;/p&gt;
&lt;h3 id=&#34;李夏亭醬蟹&#34;&gt;李夏亭醬蟹&lt;/h3&gt;
&lt;p&gt;醬蟹是少數在出發前就決定好的目標，自由活動的當天馬上直衝&lt;a href=&#34;https://maps.app.goo.gl/99iVRQhDEEKE85rM7&#34;&gt;李夏亭醬蟹&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;點餐的時候充滿了既期待又怕受傷害的心情，在用餐之前還先吃了胃藥以防萬一。&lt;/p&gt;
&lt;p&gt;吃的時候要先把飯挖到蟹殼裡，跟膏和醬攪著吃，接著把蟹肉擠到飯上面，搭配海苔一起吃。&lt;/p&gt;
&lt;p&gt;只能說「恐怖但好吃」，一拿起來就有一股神祕的蟹味，難怪有人說會腥、也有人說這是鮮味。但是的確蠻好吃的，非常蟹，需要克服。&lt;/p&gt;
&lt;p&gt;如果能夠吃生蝦之類的朋友可以嘗試看看，但如果對生食有所抗拒的還是先不要。我個人之後來韓國可能還會再吃吧，畢竟我喜歡吃螃蟹，而醬蟹真的很蟹。&lt;/p&gt;
&lt;p&gt;延伸閱讀：&lt;a href=&#34;https://vocus.cc/article/6512a52dfd89780001ded034&#34;&gt;釜山美食李河榮醬油螃蟹！來韓國必吃，蟹肉蟹黃伴飯真的凍未條&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yLoYoDCl.webp&#34;
  alt=&#34;Image&#34;width=&#34;481&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 醬蟹被吃掉之前的最後合影&lt;/p&gt;
&lt;h3 id=&#34;海東龍宮寺&#34;&gt;海東龍宮寺&lt;/h3&gt;
&lt;p&gt;自由活動的另一個景點選了海東龍宮寺，入寺要先走過十二生肖的雕塑、一個小隧道和石階，就能看見海岸邊的海東龍宮寺。&lt;/p&gt;
&lt;p&gt;寺廟裡面有蠻多景色，建築的用色比想像中鮮豔一點，到處都能看見藍綠色。除了大雄寶殿本體以外，還有可以看見整個寺廟的岩灘（&lt;a href=&#34;https://maps.app.goo.gl/cSmQEJaDuSfMUJbK7&#34;&gt;祭龍檀&lt;/a&gt;）、在地下室裡傳說有療效的泉水（當然現在不給喝了），以及景色很好的&lt;a href=&#34;https://maps.app.goo.gl/JNtYWMpx9Chv3ySn7&#34;&gt;咖啡廳&lt;/a&gt;（想喝咖啡嗎？先看一下評價）&lt;/p&gt;
&lt;p&gt;除了很有韻味的建築以外，寺內幾乎都能看見廣闊的大海。讓人覺得「在這種地方修煉會不會太舒服」(?)&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/k6EPirfl.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;361&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 大雄寶殿，藍綠色的裝飾蠻好看的，遊客人數也意外地多。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/C2o8DNDl.webp&#34;
  alt=&#34;Image&#34;width=&#34;481&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 許願池，要把銅板丟到碗裡。但應該對台灣遊客不是問題，畢竟都在夜市套圈圈訓練過了&lt;/p&gt;
&lt;h3 id=&#34;老奶奶伽倻小麥冷麵&#34;&gt;老奶奶伽倻小麥冷麵&lt;/h3&gt;
&lt;p&gt;自由活動日的最後一頓，決定探訪網路上找到、位於南浦洞巷子裡的&lt;a href=&#34;https://maps.app.goo.gl/DBi3bPRosRVMSuTa8&#34;&gt;伽倻小麥冷麵&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;上頭的醬汁拌勻調味，再用剪刀把麵剪斷，就可以開始享受Ｑ彈的麵。&lt;br/&gt;吃的時候會附一壺像是茶的飲料，喝起來像是湯。不太確定是個啥，但蠻好喝的&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GHjnOh6l.webp&#34;
  alt=&#34;Image&#34;width=&#34;481&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 冷麵本麵，哇金尬意，咻嚕嚕嚕&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UtkXIUVl.webp&#34;
  alt=&#34;Image&#34;width=&#34;481&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

▲ 另外點的蒸餃，不錯吃，因為餡很飽滿，好像很上相，所以決定也放張照片上來&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;這趟旅遊是五月多出發的，隔了半年才想說「既然 2024 都要結束了，那來記錄一篇這趟出國走過的景點吧」，所以大多時間都在看照片回憶當時的狀況。&lt;/p&gt;
&lt;p&gt;當然也省略了一些沒待很久的快閃景點，例如可以看見對馬島的白淺灘、有烏龜故事的松島等等（還有跟團一定有的推銷購物環節也省略了）&lt;/p&gt;
&lt;p&gt;除了到處吃吃喝喝以外，最大的體悟其實是英文還蠻重要的。身為一個多益 300 左右、程式碼命名還要先查單字的菜雞工程師，到了國外差點連咖啡都買不了 QQ&lt;/p&gt;
&lt;p&gt;但長這麼老了難得出國一次，真的蠻新奇的，也吃了不少新東西（畢竟到處找食物吃就是我的人生志業），突然對以後的旅遊多了蠻多興趣，因此也就開了這篇。&lt;/p&gt;
&lt;p&gt;希望來年還有機會再寫一篇吧。那麼，我們下次見～&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6W5Cgttl.webp&#34;
  alt=&#34;Image&#34;width=&#34;496&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>VSCode: 使用 LiveShare 來即時線上協作吧</title>
      <link>https://igouist.github.io/post/2024/09/vscode-live-share/</link>
      <pubDate>Sun, 01 Sep 2024 15:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/09/vscode-live-share/</guid>
      <description>&lt;p&gt;在 VSCode 躺了一段時間的 LiveShare 終於派上了用場，這邊就簡單記錄一篇，方便以後推坑的時候用。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8qQxzrk.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;447&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;當時的情況是這樣的：在風和日麗的某個下午，我和女友去客美多（Komeda）喝超讚的蜂蜜冰咖啡，順便 &lt;del&gt;看著她加班&lt;/del&gt; 悠閒地 Coding。&lt;/p&gt;
&lt;p&gt;但麻煩的是，我們倆的座位是面對面的兩人座。所以當我們要邊看程式碼邊溝通時，得先把筆電轉 180 度，或是起身走到另一側，非常不方便&lt;/p&gt;
&lt;p&gt;就在來回走了幾趟 &lt;del&gt;漸漸不耐煩&lt;/del&gt; 之後，突然想起了香香的 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsliveshare&#34;&gt;LiveShare&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LiveShare 可以讓我們在 VSCode 裡即時線上協作，一起編輯同一份程式碼&lt;/strong&gt;。&lt;br/&gt;並且也能看見對方游標的動作、在文件上留言互動等等&lt;/p&gt;
&lt;p&gt;當我們正在 WFH，或是像這種懶得走過去(?)，但又想要和朋朋們來場 Pair 的時候，LiveShare 就可以派上用場了！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：Visual Studio 應該已經內建 LiveShare，&lt;br/&gt;可以從「檔案 &amp;gt; 開始 Live Share 工作階段」來開啟&lt;/p&gt;
&lt;p&gt;而 Jetbrains 的朋朋，&lt;br/&gt;請左轉使用 &lt;a href=&#34;https://www.jetbrains.com/code-with-me/&#34;&gt;Code with me&lt;/a&gt;，同樣也能線上開嚕&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;馬上來介紹一下安裝和基本的使用方式吧。&lt;/p&gt;
&lt;p&gt;首先第一步當然是先安裝擴充套件：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zXoTEUA.webp&#34;
  alt=&#34;Image&#34;width=&#34;1233&#34; height=&#34;213&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝完畢之後，在左下角應該就能看到 Live Share 的圖示：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tFVEzq6.webp&#34;
  alt=&#34;Image&#34;width=&#34;954&#34; height=&#34;178&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;點擊之後就會開始分享囉，也會很貼心地幫你複製邀請連結：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2Q4WycJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;1109&#34; height=&#34;247&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果想再複製一次邀請連結，或是想要停止分享，就再戳一次左下角的 Live Share 圖示就可以了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DwxhLDP.webp&#34;
  alt=&#34;Image&#34;width=&#34;1481&#34; height=&#34;524&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;受邀者加入就可以開始線上協作囉！&lt;/p&gt;
&lt;p&gt;過程中可以看見對方游標所在的位置：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TPQAYSd.webp&#34;
  alt=&#34;Image&#34;width=&#34;529&#34; height=&#34;228&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;同時在右上角也會有一些功能可以使用，例如追隨對方的視角，還有直接把對方畫面拉過來的大聲公：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2axqUwE.webp&#34;
  alt=&#34;Image&#34;width=&#34;632&#34; height=&#34;186&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fxcwjFK.webp&#34;
  alt=&#34;Image&#34;width=&#34;1853&#34; height=&#34;1357&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除此之外，如果不方便通話，也可以留言討論（但大多時候還是邊通話邊改比較方便啦）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/NslgcUv.webp&#34;
  alt=&#34;Image&#34;width=&#34;1153&#34; height=&#34;312&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;基本上有這些功能就可以搞定大部份 Pair 的場景了，有興趣的朋朋也可以試玩看看，&lt;br/&gt;線上協作，簡單方便，阿彌陀佛。&lt;/p&gt;
&lt;p&gt;那麼，今天的分享就到這邊。又成功水了一篇，下篇文章見～&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>使用 AddTypeMap 調整型別映射，讓 Dapper 乖乖寫入 0001-01-01 到 datetime2</title>
      <link>https://igouist.github.io/post/2024/08/dapper-datetime-0001-01-01/</link>
      <pubDate>Sun, 25 Aug 2024 15:20:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/08/dapper-datetime-0001-01-01/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/YLnFkUH.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;339&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這週忙著打黑悟空，簡單記一下前陣子同事遇到的一個場景：&lt;/p&gt;
&lt;p&gt;由於曆法的關係，SQL Server 的 datetime 範圍只能從 1753-01-01 開始。&lt;br/&gt;如果我們要寫入 0001-01-01 進去的話就會報錯&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;補充：關於曆法的部份，有興趣的朋友可以參考這篇：&lt;br/&gt;&lt;a href=&#34;https://sharedderrick.blogspot.com/2009/09/blog-post.html&#34;&gt;淺談：消失的日期，以關聯式資料庫的日期資料類型為例 - 德瑞克：SQL Server 學習筆記&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這時候通常去把資料表欄位改成香香的 datetime2 就可以搞定，爽爽寫入 0001-01-01。&lt;/p&gt;
&lt;p&gt;但如果我們正在使用 Dapper，預設會把 C# 的 DateTime 映射到 SQL Server 的 datetime，所以還是會寫入失敗。&lt;/p&gt;
&lt;p&gt;這時候我們就需要明確地請 Dapper 幫我們使用 datetime2 來進行處理。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果傳遞的只是某個參數，使用 &lt;code&gt;DbType&lt;/code&gt; 就行了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;@Birthday&amp;#34;&lt;/span&gt;, birthday, DbType.DateTime2);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但如果傳遞的是某個 Model 裡的特定欄位呢？例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;HistoricalEvent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Guid EventId { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }       &lt;span style=&#34;color:#75715e&#34;&gt;// 歷史事件的唯一識別碼&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } &lt;span style=&#34;color:#75715e&#34;&gt;// 歷史事件的描述&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; DateTime EventDate { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } &lt;span style=&#34;color:#75715e&#34;&gt;// 歷史事件發生的日期, 可能早於 1753 年！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這種時候就可以使用 AddTypeMap 來調整一下 Dapper 的映射型別。&lt;/p&gt;
&lt;p&gt;首先確認 DB 的欄位已經修改為 datetime2，接著我們就可以使用 &lt;code&gt;SqlMapper.AddTypeMap&lt;/code&gt; 來幫 Dapper 加上一些小小的型別轉換：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 粗暴地添加 TypeMap，讓 Dapper 知道 DateTime 現在對應到 DB 的 datetime2 了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 等等 Dapper 才會乖乖地把 0001/1/1 寫進去= =&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SqlMapper.AddTypeMap(&lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt;(DateTime), System.Data.DbType.DateTime2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 示範用資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; historicalEvent = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HistoricalEvent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    EventId = Guid.NewGuid(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;公曆紀元開始&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    EventDate = DateTime.Parse(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0001-01-01&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql = 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    insert into HistoricalEvents (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        EventId, 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        Description, 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        EventDate)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    values(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @EventId, 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @Description, 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        @EventDate); 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Connection)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; conn.ExecuteAsync(sql, historicalEvent);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;備註：因為我們改的是靜態方法裡的全域設定，所以確定要調整型別映射的話，可以把 &lt;code&gt;AddTypeMap&lt;/code&gt; 移動到註冊的地方，例如 Program。&lt;/p&gt;
&lt;p&gt;方便後續維護的時候好管理，也避免其他同事不知道你偷改，減少被扁的醫療費用。&lt;/p&gt;
&lt;p&gt;此外，雖然微軟把拔也&lt;a href=&#34;https://learn.microsoft.com/zh-tw/sql/t-sql/data-types/datetime-transact-sql?view=sql-server-ver16&#34;&gt;建議改用 dateTime2&lt;/a&gt;，但如果你會想先了解這兩者的不同（例如精度）再進行處理，可以參考以下的文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tutorialsteacher.com/articles/datetime-vs-datetime2-in-sqlserver&#34;&gt;DateTime vs DateTime2 in Sql Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/ricos-note/datetime-vs-datetime2-55d0e403509c&#34;&gt;Datetime VS Datetime2 | by RiCo 技術農場 | RiCosNote | Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;大多數時候，我們不太需要寫入 0001-01-01，甚至 1753 年之前的日期&lt;br/&gt;（像是日期尚未決定之類的場合，用 Null 表示會貼切）&lt;/p&gt;
&lt;p&gt;但夜路走多了，總會遇到 0001-01-01。像是處理古老的遺留代碼、業務邏輯必須區分「還沒設定」和「沒有資料」的狀況，又或是像上面的例子，西元的開始真的就在那一天，這種時候就沒辦法了。&lt;/p&gt;
&lt;p&gt;因此順手筆記下來，以後再回來抄，阿彌陀佛。&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀&#34;&gt;延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/sql-datetime2/&#34;&gt;SQL DateTime 型別陷阱 - 黑暗執行緒&lt;/a&gt;：另一個將 C# dateTime 映射到 SQL datetime2 的例子，用來解決 SQL datetime 在毫秒的精度問題&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://riptutorial.com/dapper/example/1199/custom-mappings&#34;&gt;Dapper.NET Tutorial =&amp;gt; Custom Mapping&lt;/a&gt;：需要自製 Mapping 的時候參考&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/11231614/how-can-i-map-net-datetime-to-database-datetime2-using-dapper-to-avoid-sqldate&#34;&gt;c# - How can I map .NET DateTime to database DateTime2 using Dapper to avoid &amp;ldquo;SqlDateTime overflow&amp;rdquo; exception? - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/51879979/changing-the-dapper-mapping-for-net-datetime-to-use-dbtype-datetime2-and-reinst&#34;&gt;c# - Changing the Dapper mapping for .net DateTime to use DbType DateTime2 and reinstating it back again - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/dapper-oracle-datetime-issue/&#34;&gt;Dapper＋Oracle 之 DateTime 注意事項-黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>.Net: 使用 FeatureManagement 套件來實作功能切換（Feature Toggle）吧</title>
      <link>https://igouist.github.io/post/2024/08/dotnet-feature-flag-and-feature-management/</link>
      <pubDate>Sun, 18 Aug 2024 13:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/08/dotnet-feature-flag-and-feature-management/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/926KRip.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;569&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在做上一篇 &lt;a href=&#34;https://igouist.github.io/post/2024/08/dotnet-ioptions&#34;&gt;IOptions&lt;/a&gt; 的筆記時，剛好看到 FeatureManagement 這香東西。&lt;br/&gt;馬上來收錄一篇。順便也簡單整理一下 Feature Flag (≒Feature Toggle) 的介紹。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#認識一下-feature-flag&#34;&gt;認識一下 Feature Flag&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#環境準備microsoftfeaturemanagement&#34;&gt;環境準備：Microsoft.FeatureManagement&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#實作使用-isenabledasync-拿到-feature-flag-的值&#34;&gt;實作：使用 IsEnabledAsync 拿到 Feature Flag 的值&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#實作使用-featuregate-來啟用和停用-api&#34;&gt;實作：使用 FeatureGate 來啟用和停用 API&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#實作使用-featurefilter-自訂篩選條件隨機抽取使用者&#34;&gt;實作：使用 FeatureFilter 自訂篩選條件、隨機抽取使用者&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#實作使用-featurefilter-來根據白名單啟用-api&#34;&gt;實作：使用 FeatureFilter 來根據白名單啟用 API&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;認識一下-feature-flag&#34;&gt;認識一下 Feature Flag&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本部落格秉持著「簡單、友善、我好菜」的精神，按照慣例先簡單介紹一下&lt;br/&gt;
已經知道的朋友就可以跳過這個小節，直接前往 #環境準備 囉。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;假設我們原本有 Old 邏輯：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Old();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;天庭傳來諭令，要我們改成 New 邏輯。這簡單，我們就把 Old 砍掉，換成 New。非常自然，改完就佈版&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;New();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;隔天，天庭又傳來諭令，New 需要調整一下，先不要了&lt;br/&gt;
現在我們又需要把 New 邏輯砍掉，讓 Old 邏輯回來。&lt;br/&gt;
簡單，但看來我們得再上一版&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Old();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// New();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;再隔天，大家可能猜到天庭又要幹嘛了，總之又上了一版&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Old();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;New();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此往復三萬八千次，工程師終於受不了了：「俺老孫每天在這切換 Old 跟 New，改完還得佈版，每天搞這些就飽了，我滴媽呀，不幹了」&lt;/p&gt;
&lt;p&gt;就在老孫關燈走人的那一瞬間，突然靈光一現：等等，&lt;strong&gt;俺加個開關還不行嗎？&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (NewFunctionEnabled())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    New();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Old();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在只要把這個開關放到外面，別寫死在程式碼裡。丟去什麼 appsettings.json、Config、資料庫、ini、東海龍王廟，隨便&lt;/p&gt;
&lt;p&gt;總之丟去個不用佈板就能直接修改的地方，不就完事了嗎？&lt;/p&gt;
&lt;p&gt;現在程式碼就像盯著旗手的士兵們，旗手舉了藍色，他們就往左跑；旗手舉了紅色，他們就往右跑。老孫只要把設定給改了，就能一鍵切換邏輯，好不自在。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;沒錯，老孫剛剛發現了 Feature Flag&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;有了這個開關，除了開開關關(?)，現在也可以做到更多事情了。&lt;/p&gt;
&lt;p&gt;例如新功能想先隨機讓一些使用者踩看看，馬上在這個 &lt;code&gt;NewFunctionEnabled()&lt;/code&gt; 開關後面加點料，讓它隨機打開、隨機關起來；&lt;/p&gt;
&lt;p&gt;想讓合作好的客戶搶先使用，馬上在這個 &lt;code&gt;NewFunctionEnabled()&lt;/code&gt; 開關後面偷塞一張白名單：&lt;br/&gt;你聽好了，看到這些人就幫我按藍色的按鈕，門就會打開&lt;/p&gt;
&lt;p&gt;因為我們把開關抽出去了，代表判斷條件和執行邏輯之間的耦合被我們給降低了，&lt;br/&gt;
這樣開關的彈性就變高了，我們也就可以針對開關內部的邏輯動手動腳了&lt;/p&gt;
&lt;p&gt;老孫把開關弄出來之後，用得挺爽。一時之間，花果山分部的工程師紛紛效仿老孫，&lt;br/&gt;用這個&lt;strong&gt;香香的 Feature Flag 來從外部動態切換程式碼邏輯&lt;/strong&gt;，好不爽快。
&lt;br/&gt;吱聲此起彼落，可謂是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;兩條邏輯改又改，改完還要等佈版&lt;br/&gt;
有了 Feature Flag，切個開關就下班&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;至於後來開關越埋越多，系統服務七十二變。工程師老孫被迫跟著唐經理去聽研討會，好好學習怎麼管理這些 Feature Flag。還弄了些儀表板、過期時間等等，那又是另一個故事了…&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;故事說到這裡，雖然例子粗暴一點，但朋朋們應該對 Feature Flag 在幹嘛有點了解了（吧？）&lt;/p&gt;
&lt;p&gt;如果想要進一步認識 Feature Flag，這邊也準備了一些推薦閱讀。&lt;/p&gt;
&lt;p&gt;例如 Feature Flag 還可以用在哪、分成哪些種類；&lt;br/&gt;
兩個 Flag 共用到同一段 Code 怎辦；&lt;br/&gt;
PM 朋朋們又是怎麼運用 Feature Flag 的……&lt;/p&gt;
&lt;p&gt;都丟在下面這團，提供給有興趣的朋朋們：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://martinfowler.com/articles/feature-toggles.html&#34;&gt;Feature Toggles (aka Feature Flags) - martinFowler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mileschou.me/blog/feature-toggle-faq/&#34;&gt;Feature Toggle 應用常見問題 | Miles&amp;rsquo; Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/3pm-lab/product-beta-release-planning-and-feature-flags-implementation-1f7673007d23&#34;&gt;產品上線規劃：Beta Release、Feature Flags 實作經驗與工具分享 | by Anne Hsiao | 3PM LAB 產品三眼怪實驗室 | Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://darrensim.com/2018/introduction-to-feature-toggles-and-implementation-best-practices/&#34;&gt;Introduction to Feature Toggles and Implementation Best Practices - Darren Sim&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：雖然 Feature Flag 這麼香，但如果開關沒有好好管理，&lt;br/&gt;不小心開到不該開的程式碼邏輯會發生什麼事呢？&lt;/p&gt;
&lt;p&gt;泡杯茶，看看實例吧：&lt;a href=&#34;https://dougseven.com/2014/04/17/knightmare-a-devops-cautionary-tale/&#34;&gt;Knightmare: A DevOps Cautionary Tale – Doug Seven&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：Feature Toggle 常常跟主幹開發（Trunk-based Development）一起出現？&lt;/p&gt;
&lt;p&gt;蠻常聽到這個問題的，這邊也簡單筆記一下，沒有採用主幹開發的朋朋可以跳過這小段&lt;/p&gt;
&lt;p&gt;（想認識 Trunk-based Development 的朋朋們，&lt;br/&gt;　可以右轉去 &lt;a href=&#34;https://tw.trunkbaseddevelopment.com/&#34;&gt;TrunkBasedDevelopment.com&lt;/a&gt;，感謝協助中文化的大大們）&lt;/p&gt;
&lt;p&gt;回到問題，我的理解是這樣：&lt;/p&gt;
&lt;p&gt;即使我們採用了主幹開發，持續把變更交付到主幹分支上，把大家的 GAP 降到最低，&lt;br/&gt;但一定還是會遇到需要做很久才做得完的功能&lt;/p&gt;
&lt;p&gt;我們不想要拉一條 feature 分支，放著漸漸就跟主幹脫節，到時候合併又要大爆炸&lt;br/&gt;
而是想要把這些程式碼持續地整合到我們的程式庫，這樣就能及早發現問題和解除衝突&lt;br/&gt;
可是如果粗暴地合併進去，開發中的功能就會暴露出來…怎麼辦呢？&lt;/p&gt;
&lt;p&gt;這種時候，如果有一個開關，來幫助我們關閉尚未完工的功能、打開即將交付的功能。我們就能整合現有的程式碼，也能提早知道合併會不會炸爛其他功能…&lt;/p&gt;
&lt;p&gt;很方便對吧？ Feature Toggle 就跳出來啦。香。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;環境準備microsoftfeaturemanagement&#34;&gt;環境準備：Microsoft.FeatureManagement&lt;/h2&gt;
&lt;p&gt;好，我們前面簡單介紹了一下 Feature Flag，接著就進入實作階段。&lt;/p&gt;
&lt;p&gt;要實現 Feature Flag 的方法有很多，例如我之前都是用&lt;a href=&#34;https://igouist.github.io/post/2024/08/dotnet-ioptions&#34;&gt;上一篇&lt;/a&gt;提到的 IOption 直接從 appsettings.json 把 true/false 讀出來用&lt;/p&gt;
&lt;p&gt;而這篇我們要來使用香香的 FeatureManagement 來幫我們實現 Feature Flag 的操作。&lt;/p&gt;
&lt;p&gt;首先，我們要先搞到 Microsoft.FeatureManagement.AspNetCore 這個套件：&lt;br/&gt;&lt;a href=&#34;https://www.nuget.org/packages/Microsoft.FeatureManagement.AspNetCore&#34;&gt;NuGet Gallery | Microsoft.FeatureManagement.AspNetCore&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;接著讓我們把它給註冊到我們的 Program.cs：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 環境準備：Microsoft.FeatureManagement&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 加入 Feature Flag 功能，預設會抓 appsettings.json 中的 &amp;#34;FeatureManagement&amp;#34; 節點&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddFeatureManagement();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 如果想要自訂 Feature Flag 在 appsettings.json 裡的位置，可以這樣寫&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//builder.Services.AddFeatureManagement(builder.Configuration.GetSection(&amp;#34;CustomFeatureManagement&amp;#34;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後為了後續的示範，先開好一組 Controller：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Controllers/DemoController.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 示範 Feature Flag 的使用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ILogger&amp;lt;DemoController&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IFeatureManager _featureManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; DemoController(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ILogger&amp;lt;DemoController&amp;gt; logger, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IFeatureManager featureManager)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _featureManager = featureManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣環境就搞定了，讓我們開始第一組實作吧！&lt;/p&gt;
&lt;h2 id=&#34;實作使用-isenabledasync-拿到-feature-flag-的值&#34;&gt;實作：使用 IsEnabledAsync 拿到 Feature Flag 的值&lt;/h2&gt;
&lt;p&gt;假設我們跟上面的老孫一樣：翻寫了一條新版邏輯。&lt;br/&gt;如果開關被打開了，就走新版的路線；關著的話就維持舊版&lt;/p&gt;
&lt;p&gt;要達成上面的條件，我們會&lt;strong&gt;需要直接取得某個 Flag 的開關狀態，&lt;br/&gt;這時候就可以使用 &lt;code&gt;IsEnabledAsync&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;先到 appsettings.json 加上我們的 Feature Flag，先叫做 &lt;code&gt;NewLogicEnabled&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FeatureManagement&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/FzIALd8.webp&#34;
  alt=&#34;Image&#34;width=&#34;884&#34; height=&#34;862&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;為了後面方便使用，我習慣先建一組簡單的 Const 來管理這些 Feature Flag：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Flags/FeatureFlags.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FeatureFlags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊的名稱要和 appsettings.json 裡 FeatureManagement 底下的設定一樣&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; NewLogicEnabled = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;讓我們加上這次測試用的 API，並且使用 IsEnabledAsync 來抓取指定的 Flag 狀態：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Controllers/DemoController.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Feature Flag 可以讓我們從設定檔中讀取特定的開關&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 並且在程式中根據這些開關來決定是否啟用某些功能&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet(&amp;#34;IsEnabledAsync&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt; IsFeatureEnabled()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Demo 1: 使用 IsEnabledAsync 拿到 Feature Flag 的值 &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isEnabled = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; _featureManager.IsEnabledAsync(FeatureFlags.NewLogicEnabled);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; isEnabled 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;香香的新版邏輯已啟用。&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        : &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;新版邏輯尚未啟用，繼續使用臭臭的舊版邏輯。&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/O1uxn9W.webp&#34;
  alt=&#34;Image&#34;width=&#34;746&#34; height=&#34;528&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在讓我們直接把 appsettings.json 裡的 &lt;code&gt;NewLogicEnabled&lt;/code&gt; 改成 false（只要存檔就好了，不需要重啟服務），並且再呼叫一次 API：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WTgcapM.webp&#34;
  alt=&#34;Image&#34;width=&#34;907&#34; height=&#34;294&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;來張動圖看看成果：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9elZfT7.gif&#34;
  alt=&#34;Image&#34;width=&#34;2860&#34; height=&#34;1660&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;搞定，收工下班。&lt;/p&gt;
&lt;h2 id=&#34;實作使用-featuregate-來啟用和停用-api&#34;&gt;實作：使用 FeatureGate 來啟用和停用 API&lt;/h2&gt;
&lt;p&gt;除了根據開關決定要執行哪條路線的邏輯以外，我們也常常根據開關決定某些功能是不是要開放&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果我們需要根據 Feature Flag 來決定是否啟用某支 API，就可以使用 &lt;code&gt;FeatureGate&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先讓我們加上第二組 Flag，這次叫做 &lt;code&gt;NiceApiEnabled&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FeatureManagement&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NiceApiEnabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Flags/FeatureFlags.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FeatureFlags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; NewLogicEnabled = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// 新增 NiceApiEnabled&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; NiceApiEnabled = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NiceApiEnabled&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就來替我們的 API 掛上 FeatureGate 試試：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Controllers/DemoController.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet(&amp;#34;FeatureGate&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[FeatureGate(FeatureFlags.NiceApiEnabled)]&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 讓 FeatureGate 去檢查我們的 NiceApiEnabled&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; FeatureGate()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Demo 2: 使用 FeatureGate 來啟用和停用 API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 如果 NiceApiEnabled 是關閉的，這個方法不會被執行，會直接回傳 404&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;這是一個很棒的 API，只有你看得到。&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;NiceApiEnabled&lt;/code&gt; 是 true 的時候，可以正常呼叫到 API：



&lt;img
  src=&#34;https://image.igouist.net/2el7jVT.webp&#34;
  alt=&#34;Image&#34;width=&#34;792&#34; height=&#34;409&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而當 &lt;code&gt;NiceApiEnabled&lt;/code&gt; 是 false 的時候，這支 API 就 不存在了…



&lt;img
  src=&#34;https://image.igouist.net/BV3aomM.webp&#34;
  alt=&#34;Image&#34;width=&#34;1431&#34; height=&#34;469&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;實作使用-featurefilter-自訂篩選條件隨機抽取使用者&#34;&gt;實作：使用 FeatureFilter 自訂篩選條件、隨機抽取使用者&lt;/h2&gt;
&lt;p&gt;前面我們示範了怎麼看開關狀態，以及根據開關把 API 給 Ban 了。接著來點場景應用吧。&lt;/p&gt;
&lt;p&gt;之前遇過需要隨機抽樣去分流的狀況（把 50% 使用者引導去翻寫好的新站台觀察狀況之類的）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;像這種需要自己寫某些邏輯來決定開關是否開啟的場合，就可以使用 FeatureFilter 來實現&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;跟前兩個範例一樣，我們需要先去 &lt;code&gt;appsettings.json&lt;/code&gt; 掛上我們的 Feature Flag。既然是要隨機抽取幸運兒體驗我們的新功能，就叫做 &lt;code&gt;RandomTrial&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;但要注意，當我們要自訂 Flag 啟用的條件（也就是需要跑過某些邏輯才能決定是 true/false）的時候，就不能像前面兩個範例一樣簡單丟個 &lt;code&gt;&amp;quot;RandomTrial&amp;quot;:true&lt;/code&gt; 就搞定&lt;/p&gt;
&lt;p&gt;而是要使用 &lt;code&gt;EnabledFor&lt;/code&gt;，並在裡面標示出我們要呼叫的 FeatureFilter 名稱、需要傳遞的參數，這樣 FeatureManagement 才知道它得依序跑過這些 FeatureFilter&lt;/p&gt;
&lt;p&gt;假設我們希望有 50% 的使用者會隨機抽中新功能。我們就可以先在 &lt;code&gt;EnabledFor&lt;/code&gt; 裡掛一條叫做「Random」的 FeatureFilter，並且丟個 Percentage=50 的參數進去：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FeatureManagement&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NiceApiEnabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;RandomTrial&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;EnabledFor&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Random&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Parameters&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Percentage&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Flags/FeatureFlags.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FeatureFlags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; NewLogicEnabled = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; NiceApiEnabled = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NiceApiEnabled&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 新增 RandomTrial&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; RandomTrial = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RandomTrial&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們實作對應的 FeatureFilter，這邊作法就可以粗暴一點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;從 &lt;code&gt;context.Parameters&lt;/code&gt; 把前面設好的 Percentage 參數拉出來&lt;/li&gt;
&lt;li&gt;現場骰下去看有沒有過門檻，沒過就掰掰&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Filters/RandomFilter.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 隨機啟用 API 的 Filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;see ref=&amp;#34;https://learn.microsoft.com/zh-tw/azure/azure-app-configuration/howto-feature-filters-aspnet-core&amp;#34;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[FilterAlias(&amp;#34;Random&amp;#34;)]&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 設定 Filter 的別名，讓 appsettings.json 可以使用這個別名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RandomFilter&lt;/span&gt; : IFeatureFilter
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt;&amp;gt; EvaluateAsync(FeatureFilterEvaluationContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; percentage = context.Parameters.GetValue&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Percentage&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; seed = Guid.NewGuid().GetHashCode();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; randomNumber = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Random(seed).Next(&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isEnabled = randomNumber &amp;lt;= percentage;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Task.FromResult(isEnabled);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們要把這個 FeatureFilter 拿去註冊：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddFeatureManagement()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddFeatureFilter&amp;lt;RandomFilter&amp;gt;() &lt;span style=&#34;color:#75715e&#34;&gt;// 加入自訂的 Feature Filter. see: Filters/RandomFilter.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就像前面的兩個範例一樣，直接拿來用就好嚕&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Controllers/DemoController.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet(&amp;#34;FeatureFilter/Random/&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt; FeatureFilterRandom()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Demo 3: 實作：使用 IFeatureFilter 來隨機啟用 API - 直接查詢 Feature Flag&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isEnabled = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; _featureManager.IsEnabledAsync(FeatureFlags.RandomTrial);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; isEnabled 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;恭喜你！你抽中了香香新版邏輯的試用機會！&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        : &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;什麼事也沒發生，又是臭臭邏輯的一天。&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet(&amp;#34;FeatureFilter/Random/FeatureGate&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[FeatureGate(FeatureFlags.RandomTrial)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; FeatureFilterRandomWithFeatureGate()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Demo 3: 實作：使用 IFeatureFilter 來隨機啟用 API - 透過 FeatureGate 來啟用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;恭喜你！你抽中了很棒 API 的試用機會！&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;來張動圖看看成果：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9l8N7EE.gif&#34;
  alt=&#34;Image&#34;width=&#34;1741&#34; height=&#34;1267&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;實作使用-featurefilter-來根據白名單啟用-api&#34;&gt;實作：使用 FeatureFilter 來根據白名單啟用 API&lt;/h2&gt;
&lt;p&gt;接下來試試另一組場景，假設我們打算先讓十位合作客戶試用新功能，&lt;br/&gt;這時候也可以靠自製 FeatureFilter 來處理。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：這一節會需要用到依賴注入相關的功能，還不熟的朋友可以參見&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/&#34;&gt;依賴注入的筆記&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先按照慣例，先加上我們的 Feature Flag&lt;br/&gt;
這次就叫做 &lt;code&gt;Whitelist&lt;/code&gt;，並且只指定 FeatureFilter 的名稱就好，不需要參數：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;FeatureManagement&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;NiceApiEnabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;RandomTrial&amp;#34;&lt;/span&gt;: {}, &lt;span style=&#34;color:#75715e&#34;&gt;// 好長，省略
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Whitelist&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;EnabledFor&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Whitelist&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Flags/FeatureFlags.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FeatureFlags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; NewLogicEnabled = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NewLogicEnabled&amp;#34;&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; NiceApiEnabled = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NiceApiEnabled&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; RandomTrial = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RandomTrial&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 新增 Whitelist&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Whitelist = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Whitelist&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著，假裝一下我們在某張資料表裏面有白名單資料：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Repositories/WhitelistRepository.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IWhitelistRepository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Task&amp;lt;IEnumerable&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;&amp;gt; GetWhitelistAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WhitelistRepository&lt;/span&gt; : IWhitelistRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;IEnumerable&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;&amp;gt; GetWhitelistAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 假裝從資料庫中取得白名單 :)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Apple&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Banana&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Cat&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 小提醒：如果真的連線到資料庫，然後名單的改動頻率很低，可以考慮加個快取&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而且這個 Repo 也有好好註冊到 Program.cs。&lt;br/&gt;
同時，因為後續的示範一定也需要從 Request 拿到使用者的身分&lt;br/&gt;
這邊也註冊了 IHttpContextAccessor 來取得 Request 內容：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddHttpContextAccessor();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddSingleton&amp;lt;IWhitelistRepository, WhitelistRepository&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;延伸閱讀：&lt;a href=&#34;https://learn.microsoft.com/zh-tw/aspnet/core/fundamentals/http-context?view=aspnetcore-8.0#access-httpcontext-from-custom-components&#34;&gt;存取 ASP.NET Core 中的 HttpContext&lt;/a&gt; 的「從自訂元件存取 HttpContext」小節&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：如果 FeatureFilter 依賴對象的生命週期是 Scope 的，會需要調整一下 FeatureManagement 的註冊，請看這一小節最後的補充。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;上游的假資料準備好了，接著就讓我們來實作白名單 FeatureFilter 吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Filters/WhitelistFilter.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 白名單啟用 API 的 Filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[FilterAlias(&amp;#34;Whitelist&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WhitelistFilter&lt;/span&gt; : IFeatureFilter
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IWhitelistRepository _whitelistRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IHttpContextAccessor _httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; WhitelistFilter(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IWhitelistRepository whitelistRepository, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IHttpContextAccessor httpContextAccessor)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _whitelistRepository = whitelistRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _httpContextAccessor = httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt;&amp;gt; EvaluateAsync(FeatureFilterEvaluationContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 粗暴地從 Request 的 Header 取出使用者名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 實作時記得換成真正的使用者資訊來源，例如 JWT Token、拆好的 User 資訊、擲筊的結果，之類的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; user = _httpContextAccessor.HttpContext?.Request.Headers[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User&amp;#34;&lt;/span&gt;]; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(user))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 假裝從資料庫中取得白名單，檢查使用者是否在白名單中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 實作時記得換成真正的白名單來源 &amp;amp; 比對邏輯&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; whitelist = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; _whitelistRepository.GetWhitelistAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; whitelist.Any(x =&amp;gt; x.Equals(user, StringComparison.OrdinalIgnoreCase)); &lt;span style=&#34;color:#75715e&#34;&gt;// 插個不分大小寫，避免我等等 Demo 手殘&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;搞定，開開心心來註冊囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddFeatureManagement()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddFeatureFilter&amp;lt;RandomFilter&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddFeatureFilter&amp;lt;WhitelistFilter&amp;gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 把白名單的 FeatureFilter 掛上去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著捏支 API 來測測看：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Controllers/DemoController.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet(&amp;#34;FeatureFilter/Whitelist/&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt; FeatureFilterWhitelist(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromHeader(Name = &amp;#34;User&amp;#34;)]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; user)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Demo 4: 實作：使用 IFeatureFilter 來根據白名單啟用 API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isEnabled = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; _featureManager.IsEnabledAsync(FeatureFlags.Whitelist);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; isEnabled 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ? &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{user} 先生/小姐，你好。歡迎使用本服務。您是我們第 9999 位 VIP 會員！&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        : &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{user} 先生/小姐，你好。很抱歉，本服務尚未開放給您使用。請加入我們的 VIP 會員，取得優先體驗資格！&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;來張動圖看看成果：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/r2gIrLw.gif&#34;
  alt=&#34;Image&#34;width=&#34;1368&#34; height=&#34;1485&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;搞定，收工。又是美好的一天，準時下班囉&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：由於 FeatureManager 是 Singleton 的，而 AddFeatureFilter 會根據 FeatureManager 的生命週期來註冊 FeatureFilter，因此如果依賴對象的生命週期是 Scope，注入的時候就會發生錯誤。&lt;/p&gt;
&lt;p&gt;這時候請改用 AddScopedFeatureManagement：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// builder.Services.AddFeatureManagement();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddScopedFeatureManagement();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;參考：&lt;a href=&#34;https://github.com/microsoft/FeatureManagement-Dotnet/issues/15&#34;&gt;Unable to inject Scoped/Transient services into FeatureFilter · Issue #15 · microsoft/FeatureManagement-Dotnet&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;延伸閱讀：&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/#%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5%E7%9A%84%E4%B8%89%E7%A8%AE%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F-transientscopedsingleton&#34;&gt;依賴注入的三種生命週期 Transient、Scoped、Singleton&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;稍微玩了一下 FeatureManagement，並且拿了一些前陣子遇過的場景來實作看看&lt;/p&gt;
&lt;p&gt;原本以為只是簡單的去組態抓開關，想不到 FeatureFilter 提供的自訂條件還蠻彈性的，挺香&lt;/p&gt;
&lt;p&gt;FeatureGate 無腦噴 404 也挺爽的（警告：請跟呼叫端的朋朋喬好= = 不然很危險，生命危險）&lt;/p&gt;
&lt;p&gt;如果有想要搞個 Feature Flag 的場合，推薦可以玩玩看。&lt;/p&gt;
&lt;p&gt;那麼，今天的筆記就到這邊。我們下篇見～&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/zh-tw/azure/azure-app-configuration/use-feature-flags-dotnet-core&#34;&gt;如何在 .NET 應用程式中使用功能旗標的教學課程 | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2020/11/02/154134&#34;&gt;[料理佳餚] ASP.NET Core 的 Feature Flags（Feature Toggle） | 軟體主廚的程式料理廚房&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;延伸閱讀（搭配 Azure App Configuration）
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/zh-tw/azure/azure-app-configuration/feature-management-dotnet-reference?pivots=stable-version&#34;&gt;.NET 功能旗標管理 - Azure App Configuration | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/anyun/2022/10/16/160158&#34;&gt;使用 App Configuration 的 Feature manager 功能來實做 Feature Toggle/Flag 機制 | 黯雲端記事錄&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>.Net: 使用 IOptions 取得 appsettings.json 的設定值吧</title>
      <link>https://igouist.github.io/post/2024/08/dotnet-ioptions/</link>
      <pubDate>Sat, 03 Aug 2024 08:30:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/08/dotnet-ioptions/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/dh0Jo4t.webp&#34;
  alt=&#34;Image&#34;width=&#34;1493&#34; height=&#34;806&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;因為手邊的 .Net Core API 專案越來越多，蠻常會需要讀 appsetting.json 的 Config，每次都要重找文章有點麻煩，這邊就來筆記一篇。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#認識-appsettingsjson&#34;&gt;認識 Appsettings.json&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-ioptions-來註冊--注入&#34;&gt;使用 IOptions 來註冊 &amp;amp; 注入&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#補充addoptions--對-ioptions-加入驗證&#34;&gt;補充：AddOptions &amp;amp; 對 IOptions 加入驗證&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#補充ioptionsioptionsmonitorioptionssnapshot&#34;&gt;補充：IOptions、IOptionsMonitor、IOptionsSnapshot&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#ioptions&#34;&gt;IOptions&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#ioptionsmonitor&#34;&gt;IOptionsMonitor&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#ioptionssnapshot&#34;&gt;IOptionsSnapshot&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#延伸閱讀&#34;&gt;延伸閱讀&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#附錄試一下-ioptionsmonitor&#34;&gt;附錄：試一下 IOptionsMonitor&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;認識-appsettingsjson&#34;&gt;認識 Appsettings.json&lt;/h2&gt;
&lt;p&gt;首先簡單介紹一下 appsettings.json 是在幹嘛的：&lt;/p&gt;
&lt;p&gt;我們開發的時候，常常會需要先設定好一些服務參數、組態設定之類的設定值，再用這些設定值用來控制我們程式的某些行為。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「某功能的上限值是 &lt;code&gt;10&lt;/code&gt;」&lt;/li&gt;
&lt;li&gt;「某項開關在測試環境是 &lt;code&gt;false&lt;/code&gt;」&lt;/li&gt;
&lt;li&gt;「某服務信件的發送者要用 &lt;code&gt;noreply9527&lt;/code&gt;」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外常見的還有連線字串、寫 Log 時的 logging level 等等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這些設定值會和程式碼拆開，放在設定檔集中管理&lt;/strong&gt;，再讓程式碼從設定檔讀取相關的設定值來用就好，藉此來把設定值的管理和使用做個關注點分離。&lt;/p&gt;
&lt;p&gt;有了設定檔，要調整修改也比較方便：要增加或是修改設定值，都只要先往設定檔衝就行。並且因為兩邊拆開了，我們也就可以簡單地替換這些設定值來應對不同狀況（例如正式環境和測試環境套用兩組不同的設定檔，或是在自己電腦測試的時候快速改個值之類的），彈性可說是 UPUP！&lt;/p&gt;
&lt;p&gt;更重要的是，這樣我們就不需要把一大堆東西寫死在程式碼的各個地方，也就不會要改個值還要先搜尋整個專案再一個一個挖出來改了。&lt;del&gt;我按 Shift Ctrl F 已經按到哭&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;把設定值抽出去丟到設定檔之後，我們就得到了：集中管理設定值、方便修改和替換、減少程式碼中又重複又寫死的臭東西等等好處。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在 .Net Core 的世界裡，這個設定檔就是 appsettings.json&lt;/strong&gt;。&lt;br/&gt;前面提到的像是日誌等級、功能旗標之類的這些設定值，就會放在 appsettings.json 裡面。&lt;/p&gt;
&lt;p&gt;而當我們想要從 appsettings.json 把這些設定值給讀出來的時候，&lt;br/&gt;就可以使用我們的 IOptions 啦！&lt;/p&gt;
&lt;h2 id=&#34;使用-ioptions-來註冊--注入&#34;&gt;使用 IOptions 來註冊 &amp;amp; 注入&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;提醒：這篇的示範會用到一些些 .Net 依賴注入（DI）相關的操作。沒接觸過的朋友可以考慮先閱讀&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/&#34;&gt;菜雞新訓記：依賴注入&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;假設我們有個專案，叫做大漢防禦管理系統。專案內的 appsettings.json 有以下內容：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;StrongholdInfo&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Index&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;49&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;劍閣&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Enabled&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;General&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;姜維&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;廖化&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;張翼&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;董厥&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我們正好在開發一個新功能，需要抓到這段設定值。現在就來示範一下：&lt;/p&gt;
&lt;p&gt;首先，讓我們建立一個類別，等等用來放設定值內容（通常後綴會用 Options）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 關隘資訊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;StrongholdInfoOptions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 關隘編號&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Index { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 關隘名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 關隘啟用狀態&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Enabled { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 駐守人員&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;[]? General { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著使用 &lt;code&gt;Configure&amp;lt;T&amp;gt;&lt;/code&gt; 來註冊，並且用 &lt;code&gt;Configuration.GetSection&lt;/code&gt; 來指定這段設定值在 appsettings.json 裡的位置：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 原本就有的一些註冊...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddEndpointsApiExplorer();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddSwaggerGen();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 Configure 註冊 Option&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.Configure&amp;lt;StrongholdInfoOptions&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    builder.Configuration.GetSection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;StrongholdInfo&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;註冊好了之後，就可以使用 &lt;code&gt;IOptions&amp;lt;T&amp;gt;&lt;/code&gt; 注入到我們要用的地方囉&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DemoController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; StrongholdInfoOptions _info;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 使用 IOptions&amp;lt;T&amp;gt; 注入，並用 .Value 取得內容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; DemoController(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IOptions&amp;lt;StrongholdInfoOptions&amp;gt; info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _info = info.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet(&amp;#34;IOption&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;object&lt;/span&gt; Get()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; _info;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mcQSwu0.webp&#34;
  alt=&#34;Image&#34;width=&#34;840&#34; height=&#34;656&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;收工，搞定。就是這麼簡單。&lt;/p&gt;
&lt;h2 id=&#34;補充addoptions--對-ioptions-加入驗證&#34;&gt;補充：AddOptions &amp;amp; 對 IOptions 加入驗證&lt;/h2&gt;
&lt;p&gt;前面我們在註冊時，是使用 &lt;code&gt;Configure&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 Configure 註冊 Option&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.Configure&amp;lt;StrongholdInfoOptions&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    builder.Configuration.GetSection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;StrongholdInfo&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其實也可以使用 &lt;code&gt;AddOptions&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 Configure 註冊 Option&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.Configure&amp;lt;StrongholdInfoOptions&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    builder.Configuration.GetSection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;StrongholdInfo&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 或是使用 AddOptions，這兩個做法最後都會呼叫 Configure&amp;lt;StrongholdInfoOptions&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// see: https://github.com/dotnet/extensions/issues/514&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ps: `BindConfiguration` 是比較新的語法，以前會使用 `Bind() + builder.Configuration.GetSection()`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddOptions&amp;lt;StrongholdInfoOptions&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .BindConfiguration(StrongholdInfoOptions.SectionName);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這兩個語法最終會做同樣的事情，因為 &lt;code&gt;Bind()&lt;/code&gt; 會去呼叫 &lt;code&gt;Configure()&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/55762813/what-is-the-difference-between-services-configure-and-services-addoptionst&#34;&gt;c# - 在 ASP.NET Core 中加載配置時，services.Configure() 和 services.AddOptions&lt;T&gt;().Bind() 之間有什麼區別？- Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/dotnet/extensions/issues/514&#34;&gt;Question: AddOptions&lt;T&gt;() vs. Multiple Configure&lt;T&gt;(…) · Issue #514 · dotnet/extensions (github.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只是 &lt;code&gt;AddOptions&lt;/code&gt; 比較晚出現，並且後來又加入了更多自定義，用起來比較靈活。&lt;/p&gt;
&lt;p&gt;例如我們可以用 &lt;code&gt;ValidateDataAnnotations&lt;/code&gt; 來啟用屬性驗證：&lt;br/&gt;（可參考 Microsoft Learn &lt;a href=&#34;https://learn.microsoft.com/zh-tw/dotnet/core/extensions/options#options-validation&#34;&gt;選項模式&lt;/a&gt; 的「選項驗證」小節）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[RegularExpression(@&amp;#34;^[\u4e00-\u9fa5]&lt;/span&gt;{&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;}&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)] // 限定 1~10 個中文字
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddOptions&amp;lt;SettingsOptions&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ValidateDataAnnotations()  &lt;span style=&#34;color:#75715e&#34;&gt;// 會在呼叫 .value 的時候進行驗證&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// .Validate(config =&amp;gt; {})     // 也可以自訂驗證邏輯&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// .ValidateOnStart()          // 也可以要求在啟動時就驗證&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 假如加了 ValidateDataAnnotations 的話，取值驗證失敗會噴 OptionsValidationException&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SettingsOptions options = _config.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (OptionsValidationException ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;foreach&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; failure &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; ex.Failures) &lt;span style=&#34;color:#75715e&#34;&gt;// 畢竟可能一堆東西沒通過驗證嘛&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger.LogError(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Validation error: {FailureMessage}&amp;#34;&lt;/span&gt;, failure);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果需要自訂驗證器，可以搜尋 &lt;code&gt;IValidateOptions&lt;/code&gt;，可以自己實作 &lt;code&gt;Validate()&lt;/code&gt;，但我個人還沒遇到這麼複雜的狀況，這邊就不詳述。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Configure&lt;/code&gt; 直接又簡單，但 &lt;code&gt;AddOptions&lt;/code&gt; 比較靈活。&lt;br/&gt;我個人比較喜歡粗暴直接的做法，所以目前是都直接呼叫 &lt;code&gt;Configure&lt;/code&gt; 而已。供各位參考&lt;/p&gt;
&lt;h2 id=&#34;補充ioptionsioptionsmonitorioptionssnapshot&#34;&gt;補充：IOptions、IOptionsMonitor、IOptionsSnapshot&lt;/h2&gt;
&lt;p&gt;提了 IOptions 就不能不提他的兩位哥哥：IOptionsMonitor、IOptionsSnapshot&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;OptionsMonitorDemoController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 我們有三種 IOptions 相關的介面來取得設定檔內容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IOptions&amp;lt;StrongholdInfoOptions&amp;gt; _options;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IOptionsMonitor&amp;lt;StrongholdInfoOptions&amp;gt; _optionsMonitor;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IOptionsSnapshot&amp;lt;StrongholdInfoOptions&amp;gt; _optionsSnapshot;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊也迅速筆記一下。有興趣的朋友可以直接閱讀相關文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/wenhx/p/ioptions-ioptionsmonitor-and-ioptionssnapshot.html&#34;&gt;IOptions、IOptionsMonitor 以及 IOptionsSnapshot - wenhx - 博客园&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/zh-tw/aspnet/core/fundamentals/configuration/options?view=aspnetcore-8.0#options-interfaces&#34;&gt;ASP.NET Core 中的選項模式 | Microsoft Learn&lt;/a&gt; 的「選項介面」小節&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ioptions&#34;&gt;IOptions&lt;/h3&gt;
&lt;p&gt;用起來最簡單方便，個人推👍&lt;/p&gt;
&lt;p&gt;IOptions 會註冊為 Singleton，所以大家都會用同一組。也只有第一次建立的時候會抓設定檔的內容。後面就算跑去偷改檔案，也不會被影響（想更新？重啟站台吧）&lt;/p&gt;
&lt;p&gt;如果設定檔不太常改動的話，直接用 IOptions 簡單做一做是最方便的，也省資源。&lt;/p&gt;
&lt;h3 id=&#34;ioptionsmonitor&#34;&gt;IOptionsMonitor&lt;/h3&gt;
&lt;p&gt;IOptionsMonitor 同樣也會註冊為 Singleton，但是它會去偷聽設定檔有沒有更新。&lt;br/&gt;當設定檔有更新的時候 IOptionsMonitor 也會一起更新，所以能夠隨時取得目前版本的設定值&lt;/p&gt;
&lt;p&gt;取值的方法名稱也很明確表達這點，大家都是 &lt;code&gt;_options.Value&lt;/code&gt;，&lt;br/&gt;但 IOptionsMonitor 的是 &lt;code&gt;_options.CurrentValue&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_options = _options.Value,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_optionsMonitor = _optionsMonitor.CurrentValue,
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果我們的功能非常依賴設定值，而且又希望隨時更新（像留言區 Cash 大補充的 Hot Reload）的時候，就可以考慮使用 IOptionsMonitor。&lt;/p&gt;
&lt;p&gt;但要小心如果 API 正在處理 Request，然後又剛好正在修改設定檔的話，可能會有一些靈異現象（？）&lt;/p&gt;
&lt;p&gt;弄這篇筆記的時候也動手試了一下 IOptionsMonitor，但有點小佔版面，就放在最後的附錄（讓我之後可以回來抄）了。&lt;/p&gt;
&lt;h3 id=&#34;ioptionssnapshot&#34;&gt;IOptionsSnapshot&lt;/h3&gt;
&lt;p&gt;IOptionsSnapshot 會註冊為 Scope，所以每個請求進來的時候，都會各自去拿一次目前的設定檔內容，並且就用這一份設定檔內容處理這一次請求&lt;/p&gt;
&lt;p&gt;我個人感覺最中規中矩。吃得到設定檔的變動，但也不會像 &lt;code&gt;IOptionsMonitor&lt;/code&gt; &lt;br/&gt;搞到前一秒還是 true 下一秒就是 false🤔&lt;/p&gt;
&lt;p&gt;如果有改動 Config 的需求，又能接受下一組 Request 進來才吃到的時候（或是希望不要發生靈異現象），就可以考慮使用 IOptionsSnapshot。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;大概這樣。但我個人平常都還是 IOptions 優先，&lt;br/&gt;如果真的有需要即時反應設定檔的變動時，再把另外兩個拿出來討論吧。&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀&#34;&gt;延伸閱讀&lt;/h2&gt;
&lt;p&gt;前面介紹了 .Net Core 裡 &lt;code&gt;appsettings.json&lt;/code&gt; 和 &lt;code&gt;IOptions&lt;/code&gt; 的基本操作，也順便補充了一些簡單介紹。其他相關的操作，就放在延伸閱讀這邊，有興趣的朋朋們可以看看。&lt;/p&gt;
&lt;p&gt;如果想根據不同環境（Dev, Prd 之類的）切換不同 appsettings.json：&lt;br/&gt;
&lt;a href=&#34;https://blog.darkthread.net/blog/appsetting-by-environment/&#34;&gt;ASP.NET Core 依環境載入不同 appsetting.json 設定 - 黑暗執行緒&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果你正在搞功能旗標（Feature Flag/Feature Toggle）然後看到這篇的話，&lt;br/&gt;也可以嘗試看看 &lt;code&gt;FeatureManagement&lt;/code&gt;：&lt;br/&gt;
&lt;a href=&#34;https://igouist.github.io/post/2024/08/dotnet-feature-flag-and-feature-management/&#34;&gt;.Net: 使用 FeatureManagement 套件來實作 Feature Flag 功能切換吧&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果覺得到處都是 IOptions，想要降低對 IOptions 的依賴的話，可以綁到強型別裡：&lt;br/&gt;
&lt;a href=&#34;https://marcus116.blogspot.com/2021/11/aspnet-core-Configuration-Avoiding-IOptions-injection.html&#34;&gt;[NETCore] ASP.NET Core 使用強型別取代 IOption&lt;T&gt; 注入配置 ~ m@rcus 學習筆記&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/zh-tw/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0&#34;&gt;ASP.NET Core 的設定 | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/zh-tw/aspnet/core/fundamentals/configuration/options?view=aspnetcore-8.0&#34;&gt;ASP.NET Core 中的選項模式 | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://marcus116.blogspot.com/2019/03/how-to-get-value-appsettingsjson-in-netcore.html&#34;&gt;[.NETCore] 如何取得 appsettings.json 組態設定 ~ m@rcus 學習筆記 (marcus116.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/wenhx/p/ioptions-ioptionsmonitor-and-ioptionssnapshot.html&#34;&gt;IOptions、IOptionsMonitor以及IOptionsSnapshot - wenhx - 博客园 (cnblogs.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ludmal/net-configuration-with-ioptions-ioptionsmonitor-and-ioptionssnapshot-76e0efb0ad87&#34;&gt;.NET Configuration with IOptions, IOptionsMonitor, and IOptionsSnapshot | by Ludmal De Silva | Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;附錄試一下-ioptionsmonitor&#34;&gt;附錄：試一下 IOptionsMonitor&lt;/h2&gt;
&lt;p&gt;簡單比較一下修改 appsettings.json 後，IOptions 和 IOptionsMonitor 的資料差異，方便我以後需要複製貼上，或是哪天需要甩給朋友時使用。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; OptionsMonitorDemoController(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IOptions&amp;lt;StrongholdInfoOptions&amp;gt; options, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IOptionsMonitor&amp;lt;StrongholdInfoOptions&amp;gt; optionsMonitor)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _options = options;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _optionsMonitor = optionsMonitor;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet(&amp;#34;api/Demo/GetWithMonitor&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;object&lt;/span&gt;&amp;gt; GetWithMonitor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; before = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        OptionsName = _options.Value.Name,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        OptionsMonitorName = _optionsMonitor.CurrentValue.Name,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 在這裡下中斷點，打開 appsettings.json 手動改資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 把 &amp;#34;Name&amp;#34;: &amp;#34;劍閣&amp;#34; 改成 &amp;#34;Name&amp;#34;: &amp;#34;羅馬&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    System.Threading.Thread.Sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; after = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        OptionsName = _options.Value.Name,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        OptionsMonitorName = _optionsMonitor.CurrentValue.Name,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;object&lt;/span&gt;[] { before, after };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// [&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//   {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//     &amp;#34;optionsName&amp;#34;: &amp;#34;劍閣&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//     &amp;#34;optionsMonitorName&amp;#34;: &amp;#34;劍閣&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//   },&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//   {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//     &amp;#34;optionsName&amp;#34;: &amp;#34;劍閣&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//     &amp;#34;optionsMonitorName&amp;#34;: &amp;#34;羅馬&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//   }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>《九日》白金心得</title>
      <link>https://igouist.github.io/post/2024/08/nine-sols/</link>
      <pubDate>Thu, 01 Aug 2024 07:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/08/nine-sols/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zhwxf9c.webp&#34;
  alt=&#34;Image&#34;width=&#34;856&#34; height=&#34;498&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;




&lt;img
  src=&#34;https://image.igouist.net/K2vqk3j.webp&#34;
  alt=&#34;Image&#34;width=&#34;1091&#34; height=&#34;490&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;終於過了！九日沒有慣例的「完成所有成就」的盃，但是放結局獎盃又怕結局名字雷到路過的朋友，就截普通模式通關的盃吧。&lt;/p&gt;
&lt;p&gt;按照慣例，留一篇筆記來聊聊心得和體驗，以下有雷。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：這篇是我個人遊玩《九日》的心得。&lt;br/&gt;
如果你是在找《九日》的白金獎盃攻略，推薦你這篇：&lt;a href=&#34;https://steamcommunity.com/sharedfiles/filedetails/?id=3260202870&#34;&gt;【九日】才不是什麼全成就指南呢，哼！&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先，必須感謝當初募資的大大們有開出劇情模式，讓我解完其中一條結局（被易公痛扁）之後，可以馬上讀檔開劇情模式看另一條。（逃離研究中心的電影運鏡很棒啦，但我實在不想再走一次了囧）&lt;/p&gt;
&lt;p&gt;看完兩個結局後，能理解為什麼蠻多人會說后羿射日結局才是真結局。原本覺得只是「不同的選擇」，但通關之後我也比較喜歡這個結局，易公第三階段和最後射日的演出令人印象深刻，也充滿故事結束的感覺。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;這次的戰鬥和地圖探索體驗都很不錯，但果然首先要提的還是「道教龐克」的部份。&lt;/p&gt;
&lt;p&gt;一開始有點水土不服，就是有點熟悉感又覺得怪怪的。但越玩越覺得「哇這很不錯」。像是嗑毒草的阿農、墳墓是虛擬實境裝置、煉丹的丹藥其實是基因改造，另外還有很潮的&lt;a href=&#34;https://forum.gamer.com.tw/C.php?bsn=74687&amp;amp;snA=152&#34;&gt;太陽語&lt;/a&gt;（說是古漢語發音和倉頡拼字來的？）等等。&lt;/p&gt;
&lt;p&gt;蠻喜歡這種充滿各種腦洞的詮釋，感覺還有很多發展空間，不知道以後會不會有道教龐克宇宙？&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/auSDWXh.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;另外一些破台時比較有印象的部分，首先就是臭菜（想了一下才發現是在嘴香菜）。還有李耳墓，通過三位大佬的測試、聽了李耳的寓言，然後才意會到門口就是被砍翻的那三位大佬，體驗良好。&lt;/p&gt;
&lt;p&gt;哦對，還有茱麗葉。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/J1U4qxS.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然而最有記憶點的，果然還是第一次經過扶蝶他家門口的時候的 jump scare。&lt;/p&gt;
&lt;p&gt;發現畫面開始無限 Loop 的時候，我就躲得離螢幕超超超遠。事後還上巴哈求助詢問後面還有沒有這種嚇人玩意，真的差點嚇到棄坑 = =&amp;quot;&lt;/p&gt;
&lt;p&gt;但提到扶蝶就要稱讚一下，扶蝶戰的體驗和音樂真的很讚。&lt;/p&gt;
&lt;p&gt;節奏雖然很快，但是從 1 隻、3 隻再到 7 隻分身，這種逐漸加快、越來越密集的感覺打起來非常舒服。應該是我打完九日過程中覺得戰鬥體驗最讚的 Boss，堪比易公&lt;/p&gt;
&lt;p&gt;故事裡成員們陷入超級虛擬實境不想醒、搭配夢蝶的意境也很不錯。對戰中從後腦長出蝴蝶也很有 Feel，音樂更是相當有感（&lt;a href=&#34;https://www.youtube.com/watch?v=SQnSdBNwXuI&#34;&gt;Smile at My Cursed Dream &lt;del&gt;Lady Ethereal&amp;rsquo;s Theme&lt;/del&gt;&lt;/a&gt;），就是背景和跑圖太陰間了點&lt;/p&gt;
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/SQnSdBNwXuI?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;hr&gt;
&lt;p&gt;除了扶蝶戰以外，也聊聊其他比較有印象的戰鬥&lt;/p&gt;
&lt;p&gt;截全戰的印象九成都在背景音樂上，就是那首「&lt;a href=&#34;https://www.youtube.com/watch?v=J0CocEg0s9U&#34;&gt;苦難！哈！鑄英雄！&lt;/a&gt;」（有一段聽起來超像「人真好！郭富城！」的那首）。戰鬥過程就是中規中矩的你一刀我一刀。但截全的人氣好像蠻高的？常常看到相關的圖。&lt;/p&gt;
&lt;p&gt;另一場印象比較深的是姬的戰鬥，尤其是占卜可以自己踩想要的，所以踩得準就可以自己選補血，一直補一直爽。飛劍可以格檔、黑洞可以打斷，實在非常貼心，所以說這種神秘又古老的角色就是棒。奄老還是乖乖顧倉庫去吧。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tP9Htjv.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;至於最終戰的易公，只能說齁勝。剛進場的時候覺得揮刀速度明明很慢，卻一直抓不到格檔的感覺。有種被教鞭教訓的感覺（這就是師傅嗎）&lt;/p&gt;
&lt;p&gt;戰鬥過程中基本上就是鏘鏘鏘一直檔，檔完就抓空隙上符做輸出（不如說我八成輸出都是靠符），無量反擊用好才能進一步喝水跟補刀。打著打著有種夢回隻狼的 Feel&lt;/p&gt;
&lt;p&gt;當然第三階段被完全看不懂的飛天劍打成智障又是另一個故事了。易公第三杯水強得跟鬼一樣，難怪他會覺得變怪物是種進化，我被打到都快信了&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/RTBD3vG.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;整輪打下來的戰鬥心得是：&lt;strong&gt;一定要格檔，一定要貼符&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;很多輸出空間（像是扶蝶和易公）都只能靠貼符，越打到後期越覺得符才是主要輸出，刀反而是裝了玉石加減砍來治內傷的&lt;/p&gt;
&lt;p&gt;戰鬥充滿格檔和互動，體驗非常良好。&lt;/p&gt;
&lt;p&gt;故事部分也相當不錯，像山海 9000 支線到最後找到朋友和照片，還有風氏兄妹奢糜背後的原因（開打之前：人都死光了你們還開趴？　打完之後：太悲哀了QQ）、阿農一路走到帶領村人等等。&lt;/p&gt;
&lt;p&gt;有一小段和軒軒下棋的對話我也蠻喜歡：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「咦！我是不是要贏了！」&lt;/p&gt;
&lt;p&gt;『對，其實下到一半我就知道自己贏不了。 &lt;br/&gt;
　恭喜你，接下來可以去找下一名對手了。』&lt;/p&gt;
&lt;p&gt;「哈哈，才不要，我又不是為了贏，我只是想跟大哥下棋」&lt;/p&gt;
&lt;p&gt;『不為輸贏嗎？　&lt;br/&gt;
　哈，看來我該學的東西還真不少。』&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;最後，偷偷許願，希望九日有機會像隻狼那樣出個單純和 Boss 對戰的模式（補：出啦！讚讚！）&lt;/p&gt;
&lt;p&gt;像是扶蝶、易公都是那種會讓人想再打的 Boss。如果能手癢就上去擋兩把就好了呢～&lt;/p&gt;
&lt;p&gt;大概醬子，白金封片。感謝九日。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ErPzjaG.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>《Fate/Samurai Remnant》白金心得</title>
      <link>https://igouist.github.io/post/2024/04/fate-samurai-remnant/</link>
      <pubDate>Sun, 14 Apr 2024 05:20:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/04/fate-samurai-remnant/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nzs7CTb.webp&#34;
  alt=&#34;Image&#34;width=&#34;1073&#34; height=&#34;493&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;要開玩 DLC 的時候跳了盃，按照慣例來發一下白金心得文～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：這篇是我個人遊玩《Fate/Samurai Remnant》的心得。&lt;br/&gt;
如果你是在找《Fate/Samurai Remnant》的白金獎盃攻略，推薦你這幾篇&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://steamcommunity.com/sharedfiles/filedetails/?id=3045831681&#34;&gt;【FSR】全成就攻略及部分收集地圖&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://steamcommunity.com/sharedfiles/filedetails/?id=3050089051&#34;&gt;智周萬物（雜記本條目100%全收集）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;以下簡單列一下個人覺得的優點（當然有雷）&lt;/p&gt;
&lt;p&gt;伊織和 Saber 這對主從很有特色。尤其是主角伊織的成長很有感，也有反應在遊戲性上，後期拿到空之架式的時候割草很爽快。在師徒決戰、伊織使出祕劍的時候，身為 Fate 玩家和動畫宅真的看得非常開心。&lt;/p&gt;
&lt;p&gt;除此之外，&lt;strong&gt;二週目增加劇情、開始能漸漸窺見到主角內心異常的部份很棒&lt;/strong&gt;。劍鬼意外地很有魅力，最後的對戰和收尾方式也相當舒服，應該是歷代精神狀況最令人「蛤？」的主角了吧，真不錯。&lt;/p&gt;
&lt;p&gt;如果有一周目覺得劇情挖得不夠深入，或是主角的刻畫不夠的朋友，推薦把真結局打完。伊織現在已經成了我在 Fate 系列數一數二喜歡的主角了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/K4XVKnH.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了兩位主角之間的互動，其他主從之間的互動也還處理得還不錯，尤其是正雪和鄭成功。像是「我好好地遵守著您的命令！」這一段的張力很夠。&lt;/p&gt;
&lt;p&gt;也有可能是因為我意外地蠻喜歡「&lt;strong&gt;切換視角到其他陣營的角色來認識他們&lt;/strong&gt;」的處理方式吧。但當然也有一些橋段讓我，呃，不是很理解，例如三郎，到底在幹嘛？即使通關後已經幾個月的現在，這角色還是讓我充滿疑惑，而且操作起來也很難用。真的很難用。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CsLn2eJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;既然都聊到三郎了，接著來補一下個人認為的缺點：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;戰鬥時的破盾很煩，很拖時間&lt;/strong&gt;。可以理解說身為人類打這些妖怪是很麻煩啦，可是我都打到後期了還在幫他們刮痧，真的很拖棚。&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;有些角色的戲份少到令人意外&lt;/strong&gt;。例如第十五騎，劇情鋪了這麼久，不會真的只是為了出來打個招呼而已吧？也許 DLC 會交代吧。也許。&lt;/p&gt;
&lt;p&gt;最後，也是最令我崩潰的則是踩到超臭 Bug：&lt;br/&gt;
&lt;strong&gt;千辛萬苦蒐集完畢之後，居然沒跳白金！沒跳白金！沒跳白金！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;害我浪費了一堆時間爬文、哀號。直到放棄之後，大約四個月之後才等來了官方修復。太傷害了。&lt;/p&gt;
&lt;p&gt;如此如此，準備來開玩 DLC 慶安神前比武了。最後就貼個完成度紀念一下吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/V6FKDDw.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;哦對，然後豬真的很可愛。滿分。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/I9q9FZo.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>我買彩券是為了做一個善良的人</title>
      <link>https://igouist.github.io/post/2024/01/lottery-is-goodness/</link>
      <pubDate>Sat, 03 Feb 2024 12:02:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2024/01/lottery-is-goodness/</guid>
      <description>&lt;p&gt;&lt;strong&gt;我買彩券是為了做一個善良的人&lt;/strong&gt;&lt;br/&gt;
只要還沒開獎，我就有可能會成為億萬富翁&lt;/p&gt;
&lt;p&gt;有緊急需求？沒問題&lt;br/&gt;
東西又改壞了？沒關係&lt;br/&gt;
客戶跑來罵我們全家？當然可以&lt;/p&gt;
&lt;p&gt;一個買了彩劵的人，生活充滿希望，行事滿懷善良，心胸寬大、舉止謙讓&lt;/p&gt;
&lt;p&gt;畢竟我說不定晚上就發財了，沒必要跟你們計較這些小事。對吧&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;本文章絕無任何業配。投資一定有風險，購買樂透有賺有賠，申購前應撰寫破產聲明書&lt;/p&gt;
&lt;hr&gt;</description>
    </item>
    
    <item>
      <title>《臥龍：蒼天殞落》白金心得</title>
      <link>https://igouist.github.io/post/2023/04/wolong/</link>
      <pubDate>Wed, 05 Apr 2023 10:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2023/04/wolong/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/D2oDGXp.webp&#34;
  alt=&#34;Image&#34;width=&#34;1083&#34; height=&#34;475&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在東漢逛街快一個月，總算白金啦！&lt;/p&gt;
&lt;p&gt;這次臥龍玩起來還算挺爽快的，從第一隻 Boss 張梁老師就好好教導玩家的基礎操作。而且意外地能從黃巾之亂一路做到官渡之戰，前期戰役打好打滿，算是蠻有誠意的。&lt;/p&gt;
&lt;p&gt;白金之後就開始期待後續的三個 DLC 跟二代了。畢竟三國題材這麼多，實在不怕沒有後作嘛，但 DLC 還要等到六月，這篇就來稍微紀錄一下心得吧。&lt;/p&gt;
&lt;h2 id=&#34;戰鬥化解&#34;&gt;戰鬥＝化解&lt;/h2&gt;
&lt;p&gt;這次戰鬥主打的是「化解」系統，基本上就是在敵人攻擊的瞬間進行化解，很有中國武術的味兒，也讓我想起隻狼鼓勵玩家要提起勇氣上前拚刀的快感。&lt;/p&gt;
&lt;p&gt;初次接觸的時候會覺得這系統做得很不錯，同時帶來了風險和回報：化解失敗就會被捅一刀，而化解成功就可以提升氣勢（可以當成能量條，做任何動作都會消耗），並且得到進攻機會。&lt;/p&gt;
&lt;p&gt;除此之外，化解殺招的畫面也很帥，敵人釋放絕招然後主角行雲流水擋掉回擊，很有觀賞樂趣。&lt;/p&gt;
&lt;p&gt;因此整個遊戲的主軸就是化解敵人的攻擊和殺招，你來我往地進攻，整個戰鬥就是化解的節奏，打起來挺爽快的。&lt;/p&gt;
&lt;p&gt;到這裡我都還是覺得化解真香。但到中後期的時候就開始發現：整個戰鬥過程，就只有化解。&lt;/p&gt;
&lt;p&gt;不像隻狼，應對敵人的攻擊時，如果是一般攻擊就需要招架，而對付「危」的殺招時需要跳或識破，再加上敵人多樣的變招，帶來了有趣的即時應對，強迫玩家當下要判斷並且反擊，並且失誤就會帶來死亡，實在是「猶豫就會敗北」&lt;/p&gt;
&lt;p&gt;但在臥龍，不論敵人的任何動作，攻擊、殺招、弓箭、法術，基本上就是靠化解。戰鬥過程簡化到只剩下攻擊和化解兩個步驟，導致戰鬥很難有新的玩法。&lt;/p&gt;
&lt;p&gt;尤其可以同時防禦和化解這一點，讓化解的風險變得更小了，只需要防禦按著應付敵人的殺招就好。節奏熟悉了之後，戰鬥基本上就是等著敵人開大，跟下落式的節奏遊戲差不多&lt;/p&gt;
&lt;p&gt;只能說化解打得很爽快，卻也只需要化解，不夠痛快。&lt;/p&gt;
&lt;p&gt;這也關係到 Boss 的設計。老實說，主線通關後比較有記憶點的，只有虎牢關呂布和十萬伏特張遼，以及欺負玩家視角很難切換的影分身張讓公公。&lt;/p&gt;
&lt;p&gt;因為九成的 Boss 戰（其實小兵戰也是啦）都是抓時機化解，整場就是你帥一下，我化解你，你再帥一下，我再化解你。所以像袁紹這種原本很期待的知名 Boss 一次過的時候就覺得挺空虛的&lt;/p&gt;
&lt;p&gt;結果都在觀賞 Boss 長得怎樣，哇會飛好帥、會放雷電好帥。&lt;/p&gt;
&lt;p&gt;然後&lt;strong&gt;啪，化解！啪，化解！沒了。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;既然都提到張讓公公的 Boss 戰了，就順便抱怨一下圍毆關卡。&lt;/p&gt;
&lt;p&gt;這遊戲切換視角不知道為什麼很卡。偏偏仁王傳統的圍毆關卡還是有留下來，像是什麼「江東三虎臣」，三個人圍毆你還會放冷箭丟炸彈，還要喊話嗆你，我切個視角就忙得要死了，哪有時間，氣得半死。&lt;/p&gt;
&lt;p&gt;更氣的是我打過了然後難度下修了。謝囉。&lt;/p&gt;
&lt;h2 id=&#34;蒐集甚甚是可恨&#34;&gt;蒐集＝甚、甚是可恨&lt;/h2&gt;
&lt;p&gt;這次的蒐集成就比仁王少很多了（體感上），只有回復道具升滿、慣例的撿特定道具給 NPC 吃（仁王系列是糞球，這兒竟然是蟬殼），還有餵東西給路邊的野熊貓。&lt;/p&gt;
&lt;p&gt;偏偏前兩個看道具都還能大概知道數量，但野熊貓這個完全不記得哪裡有遇到，因為蒐集內容不會顯示在任務上，完全搞不懂哪一關有餵、哪一關沒餵，找得挺痛苦。只好全破完之後再全部關卡找一次囧&lt;/p&gt;
&lt;p&gt;這個成就的名稱叫做「甚是可愛」，巴哈有人說根本是「甚是可恨」，完全認同。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：三月底的時候改版，現在在關卡選單可以看到是否達成蒐集條件了，可是我已經解完了嗚嗚&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;劇情沒有劇情&#34;&gt;劇情＝沒有劇情&lt;/h2&gt;
&lt;p&gt;劇情就仁王系列傳統了，大家都被妖怪附身，玩家每個都要打。&lt;/p&gt;
&lt;p&gt;有心病的自己變妖怪；沒心病的被強迫變妖怪。不會變妖怪的？誰說人類就不能扁，信不信扁到變妖怪&lt;/p&gt;
&lt;p&gt;現在看到任何 NPC，都是在想到底能不能扁他，到底什麼時候能扁他。&lt;/p&gt;
&lt;p&gt;然後孫策的劇情就這樣，真是夠了。DLC 的「稱霸江東」這章節最好補完哦。&lt;/p&gt;
&lt;h2 id=&#34;優化仁王傳統&#34;&gt;優化＝仁王傳統&lt;/h2&gt;
&lt;p&gt;最後聊聊優化。不得不說，遊戲初期的優化做得蠻爛的，這也是仁王傳統了&lt;/p&gt;
&lt;p&gt;沒有關掉垂直同步的話會卡到幾乎不能玩，開場第一關的村莊就有大規模的燃燒場景，與其是新手教學，不如說是顯示卡檢定，看你電腦夠不夠玩這遊戲&lt;/p&gt;
&lt;p&gt;但還是有看到逐漸更新的誠意啦，像是前面提到的蒐集素材搞不懂哪關達成了、視角切換很痛苦等等。發售一個月左右基本都有推出優化和更新，還算是不錯了&lt;/p&gt;
&lt;p&gt;另外一個覺得需要優化的點，應該就是牙旗系統了。在搞什麼啊。&lt;/p&gt;
&lt;p&gt;牙旗系統給像我這樣的探索地圖仔帶來了不錯的獎勵，我一周目的時候也插旗子插得很開心&lt;/p&gt;
&lt;p&gt;只要在關卡裡找到旗子，插下去就會增加士氣，只要旗子插得越多、士氣值的下限就越高，而士氣值又能提升角色的戰鬥力，甚至比裝備什麼都來得直接有效&lt;/p&gt;
&lt;p&gt;所以逛街插旗子代表你的角色戰力也越強，跟 Boss 的差異也越小，士氣值少 Boss 五級的話，被 Boss 揍的傷害應該有差到一倍。&lt;/p&gt;
&lt;p&gt;因此一周目的時候，我基本上都是在逛關卡找旗子、欣賞地圖設計，然後跟 Boss 來一場平等的決戰。這時候的我不禁感嘆，的確是個好系統&lt;/p&gt;
&lt;p&gt;但第二次進關卡旗子都要重插，結果都在跑酷。天哪，我不打了行嗎&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;整體來說，優缺點還算明顯。一路化解打架的痛快感非常足夠、後續的優化也有聽到玩家的反應&lt;/p&gt;
&lt;p&gt;我個人是挺期待後續的三個 DLC，而且三國版仁王這麼大的題材，不作個二代也說不過去。&lt;/p&gt;
&lt;p&gt;考慮到之前仁王後續 DLC 的難度都加強不少，還是來二周目打個裝備好了囧。那麼，我們下次見～&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>使用 Midjourney AI 繪圖 &amp; Canva 產生 Banner 初體驗</title>
      <link>https://igouist.github.io/post/2023/01/banner-2-midjourney-and-canva/</link>
      <pubDate>Sun, 29 Jan 2023 10:01:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2023/01/banner-2-midjourney-and-canva/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MqLSU3f.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;200&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們在&lt;a href=&#34;https://igouist.github.io/post/2023/01/banner-1-system-drawing/&#34;&gt;前一篇&lt;/a&gt;嘗試自行產生簡單的文字 Banner，我也用了好一陣子（畢竟三年前的新訓文章還是沒整理完嘛）。&lt;/p&gt;
&lt;p&gt;直到前陣子逛臉書的時候，看到有人說「我都改用 &lt;strong&gt;AI 繪圖工具來產生部落格的配圖&lt;/strong&gt;了，還沒有版權問題」當下驚為天人：對呀！這不是很不錯嗎！&lt;/p&gt;
&lt;p&gt;如此如此，這般這般。立馬開始爬文來嘗試神秘的 AI 繪圖囉！&lt;/p&gt;
&lt;h2 id=&#34;初嘗試-midjourney&#34;&gt;初嘗試 Midjourney&lt;/h2&gt;
&lt;p&gt;在網路搜了一下，發現有大神已經整理 Midjourney 入坑詳細步驟，特此感謝：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.akanelee.me/2023/01/22/ai-midjouyney-tutorial-instruction/&#34;&gt;AI 繪圖教學，Midjourney 基本操作 · 嫁給 RD 的 UI Designer (akanelee.me)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這邊就稍微紀錄一下，總之先衝到 &lt;a href=&#34;https://www.midjourney.com/home/&#34;&gt;Midjourney&lt;/a&gt;，然後直接註他個冊、登他個入：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/L4aChGM.webp&#34;
  alt=&#34;Image&#34;width=&#34;777&#34; height=&#34;698&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這時候會需要登入 Discord，然後同意一些授權之後，回來首頁選左邊的 &lt;strong&gt;Join the Beta&lt;/strong&gt;，就會被邀請到 Midjourney 的 Discord 伺服器囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5jgjaTO.webp&#34;
  alt=&#34;Image&#34;width=&#34;782&#34; height=&#34;677&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;成功加入伺服器之後，可以看到左邊有一些 &lt;strong&gt;newbies 頻道&lt;/strong&gt;，新來的朋朋們會在這邊產圖玩：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1fvY8Wi.webp&#34;
  alt=&#34;Image&#34;width=&#34;231&#34; height=&#34;214&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;進入頻道後應該會看到很多人已經在玩了。讓我們到下面的對話框，&lt;strong&gt;輸入 &lt;code&gt;/imagine&lt;/code&gt; 找到指令&lt;/strong&gt;（因為太多人用了，可能輸入 &lt;code&gt;/&lt;/code&gt; 就會跳出來了）：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7EEe5wR.webp&#34;
  alt=&#34;Image&#34;width=&#34;273&#34; height=&#34;169&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就可以開始輸入咒文囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/J9xicE0.webp&#34;
  alt=&#34;Image&#34;width=&#34;378&#34; height=&#34;74&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;像我現在想要他產生一些很科幻的工程師雞，就可以輸入一些「Programming Chicken」啦之類的，咒文技術艱深，還請各位自行嘗試。&lt;del&gt;我已經試到整個產圖紀錄都是雞&lt;/del&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：前面提到的大神也有寫一篇關鍵字小技巧：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.akanelee.me/2023/01/26/ai-midjouyney-tutorial-writing-prompts/&#34;&gt;Midjourney 如何讓畫面精準呈現 · 嫁給 RD 的 UI Designer (akanelee.me)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以及另一位大神搭配 ChatGPT 產生咒文的紀錄，可以觀察關鍵字的格式和順序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://fullstackladder.dev/blog/2023/02/13/chat-gpt-prompts-midjourney-generator/?utm_source=pocket_reader&#34;&gt;[ChatGPT 咒語庫] Midjourney 咒語產生器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://fullstackladder.dev/blog/2023/02/13/chat-gpt-prompts-midjourney-analyzer/?utm_source=pocket_reader&#34;&gt;[ChatGPT 咒語庫] Midjourney 咒語解析器&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除此之外，也可以到 Midjourney 官網的 &lt;a href=&#34;https://www.midjourney.com/showcase/recent/&#34;&gt;Showcase&lt;/a&gt;，裡面的作品會列出使用的咒文。或是直接在頻道裡面觀察別人詠唱也可以&lt;/p&gt;
&lt;p&gt;或是到 Midjourney 提供的二次元繪圖版本 &lt;a href=&#34;https://nijijourney.com/zh/&#34;&gt;Nijijourney&lt;/a&gt;，註冊流程都一樣，但有支援中文頻道。像我這樣的英文渣去個中文頻道比較有機會看懂其他人詠唱咒文的邏輯啦，不然就認命 Google 翻譯了 xD&lt;/p&gt;
&lt;p&gt;如果沒有什麼方向，可以先隨便下咒嘗試一下，但要注意免費試用有產圖的次數限制哦&lt;br/&gt;
我個人大概產出 40 張圖的時候就用光免費額度了 xD&lt;/p&gt;
&lt;p&gt;（玩不夠的可以參見&lt;a href=&#34;https://www.midjourney.com/account/&#34;&gt;定價&lt;/a&gt;以及&lt;a href=&#34;https://home.gamer.com.tw/artwork.php?sn=5527646&#34;&gt;這篇巴哈的介紹&lt;/a&gt;，付費版還有比較慢但可以依直玩的 relax 模式，真香）。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;咒文輸入完之後 Midjourney 會運算一下，過陣子機器人就會 Tag 你跟你說搞定啦：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HiCtIZr.webp&#34;
  alt=&#34;Image&#34;width=&#34;785&#34; height=&#34;380&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到下面有兩排按鈕：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ｕ＝我要這張了，幫我弄大圖出來&lt;/li&gt;
&lt;li&gt;Ｖ＝給我從這張圖繼續產新的一組圖出來&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而數字則是對應從左到右從上到下的順序，也就是：&lt;br/&gt;
１２&lt;br/&gt;
３４&lt;/p&gt;
&lt;p&gt;如果都沒有喜歡的，就勇敢按下🔁（這些我都不要，重來），或是乖乖回去調整咒文的關鍵字吧&lt;/p&gt;
&lt;p&gt;如果按下Ｕ、選了圖之後就會開始幫你產生高清大圖：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/cxkIp7m.webp&#34;
  alt=&#34;Image&#34;width=&#34;1050&#34; height=&#34;139&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TlygHHt.webp&#34;
  alt=&#34;Image&#34;width=&#34;1536&#34; height=&#34;1024&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有種外星文明的感覺，超級太空雞的外星文明。&lt;/p&gt;
&lt;p&gt;到這邊我們已經會玩ＡＩ繪圖了，基本上就是動動手詠唱咒文而已。&lt;/p&gt;
&lt;p&gt;但別忘記今天來的目的：我們是來做部落格文章 Banner 的啊！&lt;/p&gt;
&lt;h2 id=&#34;嘗試調整圖片比例&#34;&gt;嘗試調整圖片比例&lt;/h2&gt;
&lt;p&gt;&lt;del&gt;總之我玩了半小時才想起來&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;既然我們是來製作文章封面的，而且根據先前的風格，至少要是&lt;strong&gt;矮矮寬寬的橫幅圖&lt;/strong&gt;，還要讓我可以有地方寫文章標題&lt;/p&gt;
&lt;p&gt;開始思考要怎麼做成部落格插圖之後，發現預設產的圖都不是我要的「矮矮寬寬的橫幅圖」，那麼就得先試試看是不是能夠調整圖片的比例囉！&lt;/p&gt;
&lt;p&gt;官方有提供 &lt;code&gt;/imagine&lt;/code&gt; 可以下的參數列表：&lt;a href=&#34;https://docs.midjourney.com/docs/parameter-list&#34;&gt;Midjourney Parameter List&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其中 &lt;a href=&#34;https://docs.midjourney.com/docs/aspect-ratios&#34;&gt;aspect-ratios（&amp;ndash;ar）&lt;/a&gt;就是我們要的調整圖片比例&lt;/strong&gt;，但目前最新版的 v4 只支援預設的 &lt;code&gt;1:1&lt;/code&gt; 以及 &lt;code&gt;3:2&lt;/code&gt;, &lt;code&gt;2:3&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更新：最新的 v5 已經可以支援自動比例囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/wvsnRgc.webp&#34;
  alt=&#34;Image&#34;width=&#34;930&#34; height=&#34;295&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有了 v5 之後，我只需要 &lt;code&gt;--v 5 --ar 3:1&lt;/code&gt; 就可以得到我想要的橫幅圖啦！&lt;/p&gt;
&lt;p&gt;不過以下還是 v4 版的血淚經驗，留著當紀念吧 xD&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;雖然可以用 &lt;code&gt;--v 3&lt;/code&gt; 來指定比較舊的 v3 版本，這樣比例就能支援到更長的 &lt;code&gt;7:4&lt;/code&gt;，但畢竟是舊版，連雞的形狀都高機率崩壞掉&lt;/p&gt;
&lt;p&gt;甚至給我炸雞：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2VmgckB.webp&#34;
  alt=&#34;Image&#34;width=&#34;865&#34; height=&#34;485&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;炸雞欸？真是科技齁，謝了 Midjourney。第 3 版真的不行啊，果然還是最新的版本香。&lt;/p&gt;
&lt;p&gt;這邊已經宣告放棄，但是是放棄調整圖片比例，而不是放棄加上文字&lt;/p&gt;
&lt;p&gt;決定先在咒文加一些 &lt;code&gt;black background,no background,simple,icon&lt;/code&gt; 之類的關鍵字，讓圖和背景顏色變單調，我再自己把圖拉寬補文字&lt;/p&gt;
&lt;p&gt;接著就是不斷的調整和重骰：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1NnhZFP.webp&#34;
  alt=&#34;Image&#34;width=&#34;1301&#34; height=&#34;823&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;終於選定了一張圖，是時候回歸動手開工的時候啦！&lt;/p&gt;
&lt;h2 id=&#34;使用-canva-直接產出橫幅圖&#34;&gt;使用 Canva 直接產出橫幅圖&lt;/h2&gt;
&lt;p&gt;&lt;del&gt;我努力過了，我的小畫家技術爛到拉圖打字都做不好&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;身為一個具有工程師美德（怠惰）的菜雞，當然是要找一下有沒有好用的服務嘛對不對。&lt;/p&gt;
&lt;p&gt;最後找到了這個 &lt;a href=&#34;https://www.canva.com/&#34;&gt;Canva&lt;/a&gt;。&lt;strong&gt;Canva 在免費使用時就有方便的拖拉編輯和足夠的範本數量&lt;/strong&gt;，非常符合我目前簡單拉個圖的超初階需求&lt;/p&gt;
&lt;p&gt;這就註冊登入到首頁，可以看到已經有一排快速開始的選項可以選擇，看了一下 600x200 的電子郵件標題最符合我要的形狀：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/P2fOyos.webp&#34;
  alt=&#34;Image&#34;width=&#34;1840&#34; height=&#34;622&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;進來之後就可以看到空白畫面和一排範本，這次我們有備而來，完全不會用到範本：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3M2NHL2.webp&#34;
  alt=&#34;Image&#34;width=&#34;1872&#34; height=&#34;929&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;總之先來上傳區，把剛剛ＡＩ的帥雞圖丟上來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/QnVrYag.webp&#34;
  alt=&#34;Image&#34;width=&#34;1872&#34; height=&#34;929&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著來調整背景顏色，上傳圖片之後 Canva 就會提供圖片內的一些顏色給你選，不過我試了一下，感覺上面選取顏色再指定圖片的顏色會比較好調整：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TTwi7by.webp&#34;
  alt=&#34;Image&#34;width=&#34;1872&#34; height=&#34;929&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;顏色盡量做到不太明顯就好了，畢竟ＡＩ也不會乖乖聽話用純黑背景Ｑ＿Ｑ&lt;/p&gt;
&lt;p&gt;最後切換到文字頁面，加個標題、選個字型，完工&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/0isGXEL.webp&#34;
  alt=&#34;Image&#34;width=&#34;1872&#34; height=&#34;929&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5RzC6yg.webp&#34;
  alt=&#34;Image&#34;width=&#34;1872&#34; height=&#34;929&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;怎麼感覺比我原本用的ＦＢ封面產生器還方便了呀…&lt;/p&gt;
&lt;p&gt;總之這下圖也產出來了，這篇也記錄完了。關門，收工&lt;/p&gt;
&lt;h2 id=&#34;心得小記&#34;&gt;心得＆小記&lt;/h2&gt;
&lt;p&gt;這篇稍微紀錄了一下產製部落格文章 Banner 時碰了ＡＩ繪圖和線上製圖服務的過程&lt;/p&gt;
&lt;p&gt;不得不說ＡＩ繪圖還真的蠻好玩的，唯一的問題就是 Midjourney 的免費額度一下就用光了囧&lt;/p&gt;
&lt;p&gt;額度用光之後也嘗試把 &lt;code&gt;programming chicken&lt;/code&gt; 這組關鍵字，丟去其他的ＡＩ繪圖服務看看，例如 &lt;a href=&#34;https://openai.com/dall-e-2/&#34;&gt;DALL.E&lt;/a&gt; 和 &lt;a href=&#34;https://stablediffusionweb.com/&#34;&gt;Stable Diffusion&lt;/a&gt;，目前還是 Midjourney 產的圖最精細，Stable Diffusion 甚至一直丟給我人類的圖…&lt;/p&gt;
&lt;p&gt;當然也有因為 Midjourney 跟一群人擠在 Discord 頻道產圖的體驗蠻特別的關係，看別人下咒生圖實在是蠻有趣的。我在製作工程師科技雞的時候，有位老兄一直在做帥氣烏鴉圖，差點就變成鳥類探索頻道&lt;/p&gt;
&lt;p&gt;另外還發現 Midjourney 也出了二次元特化版 &lt;a href=&#34;https://nijijourney.com/zh/&#34;&gt;Nijijourney&lt;/a&gt;，甚至支援用中文組咒文（會自動翻譯成英文），不過額度用完了就沒試，後續有機會也想玩看看&lt;/p&gt;
&lt;p&gt;最後找到的 Canva 作圖比當初的產生器還簡單，之後沒梗圖用的時候應該會優先用 Canva 吧。大概。&lt;/p&gt;
&lt;p&gt;最後用一張 OpenAI 家的 DALL.E 產生出來的圖來做結吧，感謝各位&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8C23Q5n.webp&#34;
  alt=&#34;Image&#34;width=&#34;1019&#34; height=&#34;990&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>使用 .Net 的 System.Drawing 產生簡單的文字 Banner 初體驗</title>
      <link>https://igouist.github.io/post/2023/01/banner-1-system-drawing/</link>
      <pubDate>Sun, 29 Jan 2023 10:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2023/01/banner-1-system-drawing/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aAfz8Dy.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;原本我都是用產生 FB 封面的「康熙字典體產生器」來做簡單的文字 Banner，就拿來當作文章的封面照&lt;/p&gt;
&lt;p&gt;用了好一陣子也沒啥問題。結果某天文章寫好，吃著火鍋唱著歌，產生器打開一看，服務竟然就沒了！&lt;/p&gt;
&lt;p&gt;當下是一個震驚啊，一氣之下決定直接打開 Linqpad 寫一個。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：現在搜尋康熙字典體產生器，還查得到介面截圖，還真的蠻簡單方便的 Q_Q&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;以前面的菜雞與物件導向系列 Banner 為例，我們大概需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;產生一張圖&lt;/li&gt;
&lt;li&gt;在圖上面放主標題和副標題&lt;/li&gt;
&lt;li&gt;關鍵字可以上色&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/LiILeSl.webp&#34;
  alt=&#34;Image&#34;width=&#34;783&#34; height=&#34;276&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;稍微搜尋一下發現 .Net 已經有 System.Drawing 這個工具可以幫我們完成這些簡單的圖片任務，事不宜遲馬上就來嘗試！&lt;/p&gt;
&lt;h2 id=&#34;開工&#34;&gt;開工&lt;/h2&gt;
&lt;p&gt;首先第一步，當然是先畫出橫幅底圖，&lt;strong&gt;這邊就讓我們先建立圖片，並且用 &lt;code&gt;Graph.Clear&lt;/code&gt; 先塗個黑色好辨識&lt;/strong&gt;，長寬就參照先前產生器的長 315 寬 850：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; width = &lt;span style=&#34;color:#ae81ff&#34;&gt;850&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; height = &lt;span style=&#34;color:#ae81ff&#34;&gt;315&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; img = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Bitmap(width, height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; graph = Graphics.FromImage(img))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 先塗個黑色當底&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			graph.Clear(Color.Black);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Linqpad 的打印語法，可以直接顯示圖片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		img.Dump(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qEh3RSj.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們開始加入主標題，這邊就用先前 Fluent Validation 文章的標題當作示範，直接&lt;strong&gt;用 &lt;code&gt;Graph.DrawString&lt;/code&gt; 把文字寫到圖片中間&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：這邊的字型是&lt;a href=&#34;https://www.sentyfont.com/index.htm&#34;&gt;漢儀新蒂&lt;/a&gt;的&lt;a href=&#34;https://www.sentyfont.com/chalk.htm&#34;&gt;黑板報體&lt;/a&gt;，可以替換成自己電腦裡有的字體&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; width = &lt;span style=&#34;color:#ae81ff&#34;&gt;850&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; height = &lt;span style=&#34;color:#ae81ff&#34;&gt;315&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 主標題相關設定：字型、大小、顏色&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;使用 Fluent Validation \n來驗證參數吧&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hanyi Senty Chalk Original&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontSize = &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontColor = Color.White;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; img = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Bitmap(width, height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; graph = Graphics.FromImage(img))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			graph.Clear(Color.Black);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 建立一下文字和筆刷&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; font = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Font(fontName, fontSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; brush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SolidBrush(fontColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 設定一下位置，Banner 的字當然要置中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rect = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Rectangle(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, width, height);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; format = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; StringFormat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				Alignment = StringAlignment.Center,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				LineAlignment = StringAlignment.Center
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 直接把整個標題畫到圖上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			graph.DrawString(title, font, brush, rect, format);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		img.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DDFKJFx.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們順利地把標題顯示在圖的正中間，好像有那麼一點搞頭了。&lt;/p&gt;
&lt;p&gt;接下來我們要加入關鍵字的顏色。原先的字典檔產生器可以用 &lt;code&gt;% %&lt;/code&gt; 來把要標顏色的關鍵字框起來，例如：「使用 %Fluent Validation% 來驗證參數吧」這樣就知道「Fluent Validation」是關鍵字，需要上色。&lt;/p&gt;
&lt;p&gt;因此這邊最快的做法就是用 &lt;code&gt;%&lt;/code&gt; 來切割，再照著「一般顏色、&lt;code&gt;%&lt;/code&gt;、關鍵字顏色、&lt;code&gt;%&lt;/code&gt;、一般顏色」的順序輪流上色就好了。上色也只需要&lt;strong&gt;替換設定筆刷的 &lt;code&gt;SolidBrush&lt;/code&gt; 就好&lt;/strong&gt;，非常簡單：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; width = &lt;span style=&#34;color:#ae81ff&#34;&gt;850&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; height = &lt;span style=&#34;color:#ae81ff&#34;&gt;315&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;使用%Fluent Validation%\n來驗證參數吧&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hanyi Senty Chalk Original&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontSize = &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontColor = Color.White;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 加入關鍵字要用的第二組文字顏色&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontSubColor = Color.Goldenrod; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; img = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Bitmap(width, height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; graph = Graphics.FromImage(img))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			graph.Clear(Color.Black);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; font = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Font(fontName, fontSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; brush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SolidBrush(fontColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 加入了關鍵字用的第二組筆刷&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subBrush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SolidBrush(fontSubColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rect = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Rectangle(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, width, height);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; format = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; StringFormat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				Alignment = StringAlignment.Center,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				LineAlignment = StringAlignment.Center
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 為了照一般色、關鍵字色、一般色的順序上色，需要先把標題做拆分&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; words = title.Split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; i = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &amp;lt; words.Count(); i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// 有 %% 圈選起來的字詞，使用特殊色 ex: %API%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; thisBrush = i % &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; == &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					? brush
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					: subBrush;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 改成分別將每一組字畫到圖上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				graph.DrawString(words[i], font, thisBrush, rect, format);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		img.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mQuunSa.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;好的完蛋。&lt;/p&gt;
&lt;p&gt;因為前面的 &lt;code&gt;Graph.DrawString&lt;/code&gt; 是無腦把整個 &lt;code&gt;title&lt;/code&gt; 丟進去產生，因此文字的位置只需要在 &lt;code&gt;StringFormat&lt;/code&gt; 指定個 &lt;code&gt;Center&lt;/code&gt; 讓他置中就沒問題了。但&lt;/p&gt;
&lt;p&gt;現在又要切關鍵字啥的，全部指定在中間當然就會疊在一起囧。這下子就只能自行計算長度了。&lt;/p&gt;
&lt;p&gt;因為用 &lt;code&gt;%&lt;/code&gt; 切完關鍵字之後會有很多段字，最直接的做法還是先嘗試抓到上一段字的結束位置，再繼續寫下一段字在後面，這時候我們就可以用上&lt;strong&gt;計算文字大小的 &lt;code&gt;graph.MeasureString&lt;/code&gt;&lt;/strong&gt;，馬上進行調整：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; width = &lt;span style=&#34;color:#ae81ff&#34;&gt;850&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; height = &lt;span style=&#34;color:#ae81ff&#34;&gt;315&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;使用%Fluent Validation%\n來驗證參數吧&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hanyi Senty Chalk Original&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontSize = &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontColor = Color.White;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontSubColor = Color.Goldenrod;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;  img = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Bitmap(width, height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; graph = Graphics.FromImage(img))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			graph.Clear(Color.Black);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; font = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Font(fontName, fontSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; brush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SolidBrush(fontColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subBrush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SolidBrush(fontSubColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rect = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Rectangle(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, width, height);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; format = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; StringFormat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 放棄直接指定置中的語法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;//Alignment = StringAlignment.Center,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;//LineAlignment = StringAlignment.Center&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; words = title.Split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; i = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &amp;lt; words.Count(); i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// 有 %% 圈選起來的字詞，使用特殊色 ex: %API%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; thisBrush = i % &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; == &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					? brush
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					: subBrush;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				graph.DrawString(words[i], font, thisBrush, rect, format);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// 嘗試計算每一段字的長度，然後重新調整位置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 讓下一段字可以接著繼續寫&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wordWidthFloat = graph.MeasureString(words[i], font).Width;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wordWidth = Convert.ToInt16(wordWidthFloat);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				rect.Offset(wordWidth, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		img.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7XKKyuH.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看起來使用 &lt;code&gt;Graph.MeasureString&lt;/code&gt; 先抓到字段的長度再繼續寫下去是可行的。同時我們也知道了幾件事：&lt;/p&gt;
&lt;p&gt;因為 &lt;code&gt;StringAlignment.Center&lt;/code&gt; 的置中已經被拿掉了，現在我們除了要自行計算長度，還得要自己指定起始位置了，不然會跑回左上角。這時候就需要&lt;strong&gt;回來調整處理位置的 &lt;code&gt;Rectangle&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;幸好圖片的長寬都是我們先知道的，所以這部分直接用整個 Banner 的長度除以二先定位到正中間，然後再往左邊移整行字的長度的一半，這樣就可以讓這行字置中且左右對稱了！&lt;/p&gt;
&lt;p&gt;除了起始位置以外，還可以看到 &lt;code&gt;\n&lt;/code&gt; 切出去的第二行字的開始位置也會被第一行字的結束位置影響到，這代表我們換行顯示的時候需要重新調整位置。&lt;/p&gt;
&lt;p&gt;至於第一行要顯示的高度，就要直接測一下了。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; width = &lt;span style=&#34;color:#ae81ff&#34;&gt;850&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; height = &lt;span style=&#34;color:#ae81ff&#34;&gt;315&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;使用%Fluent Validation%\n來%驗證參數%吧&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hanyi Senty Chalk Original&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontSize = &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontColor = Color.White;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fontSubColor = Color.Goldenrod;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;  img = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Bitmap(width, height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; graph = Graphics.FromImage(img))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			graph.Clear(Color.Black);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; font = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Font(fontName, fontSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; brush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SolidBrush(fontColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subBrush = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SolidBrush(fontSubColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rect = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Rectangle(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, width, height);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; format = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; StringFormat();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 先把每一行字都拆開，方便後續換行處理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; contents = title.Split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; lines = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;foreach&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; content &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; contents)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 每一行的開頭重新定位起始位置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; contentWidthFloat = graph.MeasureString(content, font).Width;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; contentWidth = Convert.ToInt16(contentWidthFloat);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 先從整張圖的寬度抓到正中間，再往左移動這行字的一半，讓整行可以置中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; setX = (width / &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;) - (contentWidth / &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 先抓整張圖中間偏上的位置，然後換行的時候就往下移動一個字的高度&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; setY = height / &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; + lines * font.Height;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 中英文混著抓距離的話有時候點歪歪的= = 這邊加個值方便手動校正起始點&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; widthset = &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 重新定位要寫字上去的起始點&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				rect.Location = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Point(setX + widthset, setY); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; words = content.Split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; i = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &amp;lt; words.Count(); i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; thisBrush = i % &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; == &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;						? brush
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;						: subBrush;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					graph.DrawString(words[i], font, thisBrush, rect, format);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wordWidthFloat = graph.MeasureString(words[i], font).Width;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wordWidth = Convert.ToInt16(wordWidthFloat);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					rect.Offset(wordWidth, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				lines++;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		img.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8jdsGY3.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看起來主標題差不多有達到想要的效果了，順便多加了一組關鍵字測一下。&lt;del&gt;程式碼的巢狀也越疊越多了哦&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;是時候來加個副標題了。副標題通常都比較簡單，像上面的範例只是用來標個系列文名稱&lt;/p&gt;
&lt;p&gt;這邊直接抓主標題的程式碼稍微改一下，接在主標題後面操作就好&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subTitle = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞新訓記‧七&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subFontSize = &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 中間處理主標題的部分省略...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 副標題&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (subTitle != &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.IsNullOrEmpty(subTitle) &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 副標題的字型，只有大小先跟主標題不一樣，高度則往下抓一些避免蓋到主標&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subFont = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Font(fontName, subFontSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subRect = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Rectangle(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, height - height / &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, width, height);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subFormat = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; StringFormat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Alignment = StringAlignment.Center,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        LineAlignment = StringAlignment.Near
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    graph.DrawString(subTitle, subFont, brush, subRect, subFormat);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3wlBfzT.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就搞定啦！&lt;/p&gt;
&lt;p&gt;最後稍微整理一下程式碼，把主標和副標會用到的參數包個類別，方便後面需要調整的時候可以用：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BannerString&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; FontName { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hanyi Senty Chalk Original&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Content { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; FontSize { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Color MainColor { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = Color.White;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Color SubColor { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = Color.Goldenrod;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;製作 Banner 的語法也先裝起來，改成傳遞上面包好類別的函式。&lt;/p&gt;
&lt;p&gt;用來調整位置的偏移值也抽出來放著方便隨時調整：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; widthset = &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; GenerateBanner(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    BannerString title,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    BannerString subTitle = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; width = &lt;span style=&#34;color:#ae81ff&#34;&gt;850&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; height = &lt;span style=&#34;color:#ae81ff&#34;&gt;315&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; img = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Bitmap(width, height))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; graph = Graphics.FromImage(img))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 上面的程式碼本體&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣後續回來使用這個腳本就可以直接改外面的參數就好啦！&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; BannerString
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       Content = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;使用%Fluent Validation%\n來%驗證參數%吧&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       FontSize = &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subTitle = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; BannerString
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       Content = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞新訓記‧七&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       FontSize = &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   GenerateBanner(title, subTitle);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3wlBfzT.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;存完收工！&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;雖然這個工具的場景挺侷限的，但寫小腳本的過程也還算有趣，&lt;del&gt;雖然調位置的時候已經有點沒耐心，快要打開小畫家了&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;有鑒於三年前新訓的筆記到今天都還沒寫完，之後應該還是會派上用場 xD&lt;/p&gt;
&lt;p&gt;但後來因緣際會看到另一種 Banner 製作方式令我躍躍欲試，結果忙半天又走回去用 Canva 直接產圖，又是&lt;a href=&#34;https://igouist.github.io/post/2023/01/banner-2-midjourney-and-canva/&#34;&gt;別的故事&lt;/a&gt;了…&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Polyglot Notebooks －－ 在 VSCode 簡單迅速地撰寫 C# 腳本吧</title>
      <link>https://igouist.github.io/post/2023/01/polyglot-notebooks/</link>
      <pubDate>Sat, 28 Jan 2023 18:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2023/01/polyglot-notebooks/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vZzccvm.webp&#34;
  alt=&#34;Image&#34;width=&#34;843&#34; height=&#34;189&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2026-03 補充：&lt;/p&gt;
&lt;p&gt;Polyglot Notebooks 已經宣佈被棄用了：&lt;a href=&#34;https://github.com/dotnet/interactive/issues/4163&#34;&gt;DEPRECATION ANNOUNCEMENT: Polyglot Notebooks &amp;amp; .NET Interactive - Github&lt;/a&gt;，由 .Net 10 提供的單檔執行功能取代。&lt;/p&gt;
&lt;p&gt;單檔執行功能可以直接用 &lt;code&gt;dotnet run app.cs&lt;/code&gt; 這種方式直接跑 &lt;code&gt;.cs&lt;/code&gt; 檔，引入套件的語法也差不多。以順手撰寫腳本的角度來說比 Polyglot Notebooks 更加方便，有興趣的朋友也可以看看：&lt;a href=&#34;https://blog.miniasp.com/post/2025/06/01/Experience-the-cool-new-features-of-dotnet-run-file&#34;&gt;體驗 .NET 10 的超酷新功能：dotnet run app.cs | The Will Will Web&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;像我這樣的菜鳥工程師，&lt;strong&gt;在 Coding 的時候時常會需要簡單試一些想法&lt;/strong&gt;。像是「我這邊下了這串 Linq 出來的資料，到底是不是我想要的內容啊 = =？」&lt;/p&gt;
&lt;p&gt;或是在進行開發任務時：「這個步驟能不能這樣做啊？先拆個簡單的 Method 試試看好了」之類的&lt;/p&gt;
&lt;p&gt;以往遇到這種時候，我都會打開香香的 &lt;a href=&#34;https://kevintsengtw.blogspot.com/2011/09/linqpad-net.html&#34;&gt;Linqpad&lt;/a&gt; 直接無情開寫，快速地作個小小的概念驗證。&lt;/p&gt;
&lt;p&gt;畢竟 Linqpad 可以迅速地開始撰寫簡單的 C# 腳本，並且有良好的語法提示、Nuget 支援和方便的資料庫連線，省去我還要開一個 Console 專案自己弄這些東西，更可以存著之後備查，所以一直以來我都愛不釋手。&lt;/p&gt;
&lt;p&gt;但在因緣際會下（其實就是閒逛論壇的時候），發現了微軟把拔出的 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode&#34;&gt;Polyglot Notebooks&lt;/a&gt; 這款 VSCode 擴充套件。當下驚為天人！Linqpad 在我心中的地位就這麼動搖了&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Polyglot Notebooks 可以讓我們直接在 VSCode 上面撰寫簡單的 C#、F#、JavaScirpt 等腳本、迅速驗證想法。甚至可以對程式碼分段、加入 Markdown 文檔，讓我們能更有邏輯、有步驟地撰寫我們的腳本、處理我們的資料。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它的特色是這麼對我的胃口，看來我以後鐵定是會用到的。現在就來簡單記錄 Polyglot Notebooks 的使用方式吧！&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝及建立檔案&#34;&gt;安裝及建立檔案&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#開始撰寫腳本&#34;&gt;開始撰寫腳本&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#切換腳本語言加入更多儲存格&#34;&gt;切換腳本語言、加入更多儲存格&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#插入-markdown-儲存格&#34;&gt;插入 Markdown 儲存格&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#印出參數內容--簡單的-linq-操作範例&#34;&gt;印出參數內容 &amp;amp; 簡單的 Linq 操作範例&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#安裝-nuget-套件--簡單的呼叫-api-範例&#34;&gt;安裝 Nuget 套件 &amp;amp; 簡單的呼叫 API 範例&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是-magic-commands&#34;&gt;什麼是 Magic Commands&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-set-來跨語言傳遞變數&#34;&gt;使用 #!set 來跨語言傳遞變數&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;安裝及建立檔案&#34;&gt;安裝及建立檔案&lt;/h2&gt;
&lt;p&gt;首先我們需要先在 VSCode 找到「Polyglot Notebooks」這個擴充套件並安裝：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/RHsdh6J.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;235&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝完畢之後，我們可以有兩種開啟方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新增 ipynb 檔案再切換到 .NET Interactive 引擎&lt;/li&gt;
&lt;li&gt;使用 Polyglot Notebooks 的指令或快捷鍵，直接建立 dib 或 ipynb 檔案&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：ipynb 檔案是用於撰寫 IPython Notebook 的檔案，也就是 Jupyter Notebook 在使用的檔案。而微軟把拔藉由 .NET Interactive 引擎讓 Jupyter Notebook 可以支援 C#, F#, SQL 等語言，讓 Notebook 可以支援多語言的撰寫，因此我們建立 ipynb 檔案也是可以撰寫 C# 的，感謝微軟把拔&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;由於我之前已經有在使用 Jupyter Notebook 撰寫 Python，因此這邊就以新增一個 ipynb 檔案進行示範：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/kjSzEzo.webp&#34;
  alt=&#34;Image&#34;width=&#34;328&#34; height=&#34;110&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Z6gdYDa.webp&#34;
  alt=&#34;Image&#34;width=&#34;584&#34; height=&#34;140&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;建立了 ipynb 檔案之後，讓我們&lt;strong&gt;切換到 .NET Interactive 引擎&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/wntas5a.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;206&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我們已經可以看到讓我們 Coding 的儲存格了，這樣就完成準備工作啦！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：也可以使用 &lt;code&gt;Ctrl+Shift+Alt+N&lt;/code&gt; 快捷鍵，或是 &lt;code&gt;Ctrl+Shift+P&lt;/code&gt; 叫出 VSCode 的指令窗來下 &lt;code&gt;Polyglot Notebook: Create new blank notebook&lt;/code&gt; 來直接建立檔案&lt;/p&gt;
&lt;p&gt;這時候 VSCode 會詢問你要建立 &lt;code&gt;.dib&lt;/code&gt; 或是 &lt;code&gt;.ipynb&lt;/code&gt;，其中 &lt;code&gt;.dib&lt;/code&gt; 進去就直接是 .NET Interactive 引擎，但目前還在實驗階段，並且 &lt;code&gt;ipynb&lt;/code&gt; 還是以較常見的格式，因此我都還是選擇慣用的 &lt;code&gt;.ipynb&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;開始撰寫腳本&#34;&gt;開始撰寫腳本&lt;/h2&gt;
&lt;p&gt;首先當然要從 Hello world 開始啦，讓我們直接在儲存格裡面開寫：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/g4T1oHZ.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;230&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;撰寫，然後&lt;strong&gt;按下全部執行或是左邊的單格執行&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7Vj66zj.webp&#34;
  alt=&#34;Image&#34;width=&#34;1756&#34; height=&#34;1141&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;切換腳本語言加入更多儲存格&#34;&gt;切換腳本語言、加入更多儲存格&lt;/h3&gt;
&lt;p&gt;儲存格的右下角可以讓我們&lt;strong&gt;切換語言&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KNAYSfy.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;320&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;例如說我們可以來個 JavaScript：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VZ3VmcX.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;222&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當然我們並不是只有這一個小小的儲存格能用（否則我前面就不敢說可以切步驟了嘛）&lt;/p&gt;
&lt;p&gt;可以&lt;strong&gt;按下左上角的「＋程式碼」來新增程式碼儲存格&lt;/strong&gt;，或是把滑鼠移到儲存格最底部，會出現執行之後幫你往下新增一格的按鈕：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/R5gie4R.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;279&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我們可以同時放上兩個語言啦：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aXXsoMa.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;366&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們也可以針對某一格來執行，或是用滑鼠選擇指定的儲存格，就可以選擇執行指定的儲存格以上或以下的部份，如果有修改某一格，然後要從修改的部分重跑一次的時候還挺方便的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1pSg3Op.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;368&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;此外還有複製貼上儲存格、合併儲存格等等操作，可以在上圖右邊紅框的「…」找到，這邊就不再贅述。&lt;/p&gt;
&lt;h3 id=&#34;插入-markdown-儲存格&#34;&gt;插入 Markdown 儲存格&lt;/h3&gt;
&lt;p&gt;眼尖的朋友應該發現了，上面新增儲存格的地方除了「＋程式碼」以外，還有「＋Markdown」的選項&lt;/p&gt;
&lt;p&gt;畢竟 Polyglot Notebooks 這東西還是一本「筆記本」，只能貼 Code 不能寫筆記還算什麼筆記本呢，對吧？&lt;/p&gt;
&lt;p&gt;因此我們可以&lt;strong&gt;在程式碼區塊之間適當地加入 Markdown 區塊，來提供需要的資訊，或是紀錄需要的筆記&lt;/strong&gt;。讓我們撰寫的過程可以更有條理，之後回來閱讀也可以更快掌握內容。&lt;/p&gt;
&lt;p&gt;除了在最底下新增儲存格的做法以外，我們也可以對指定的區塊上下來插入新儲存格：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fXuBzU6.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;368&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在讓我們加入 Markdown 內容：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/x1sEIc6.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;511&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;執行之後就會變成文檔的一部分囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/18uDSVw.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;462&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果 Markdown 有用到標題之類的，實測也可以進行摺疊：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/563QFBt.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;556&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如此一來這份 Notebook 也可以當作一份文件來保存下去了，豈不美哉！&lt;/p&gt;
&lt;h3 id=&#34;印出參數內容--簡單的-linq-操作範例&#34;&gt;印出參數內容 &amp;amp; 簡單的 Linq 操作範例&lt;/h3&gt;
&lt;p&gt;現在來一點實境題：假設我現在想要試試看怎麼「取出列表裡面，依據特定欄位分組後，每一組的第一筆資料」&lt;/p&gt;
&lt;p&gt;首先我們先做個測試用的 Class，並且捏一點測試用的假資料&lt;/p&gt;
&lt;p&gt;接著&lt;strong&gt;直接呼叫參數來印出內容確認&lt;/strong&gt;一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/r5zt5Dm.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;965&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我想要每一個 code 的第一筆，也就是 True 的那一筆。現在我覺得「也許 GroupBy 之後 Select 第一筆就搞定了吧？」&lt;/p&gt;
&lt;p&gt;這時候我就可以在下面新增一個儲存格來做測試：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DeP1PMH.webp&#34;
  alt=&#34;Image&#34;width=&#34;1012&#34; height=&#34;231&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;搞定！現在我有信心使用這段 Code 了。還可以順便存成一個「分組取第一筆.ipynb」來讓以後的我可以抄，又是美好的一天。&lt;/p&gt;
&lt;h3 id=&#34;安裝-nuget-套件--簡單的呼叫-api-範例&#34;&gt;安裝 Nuget 套件 &amp;amp; 簡單的呼叫 API 範例&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.4.20 更新：後來時常連公司自家的 Nuget 抓內部套件，決定回來補一下指定 Nuget 的範例&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;平常開發的時候，我們還會運用各種套件來完成目標。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在 Polyglot Notebooks 裡可以使用 &lt;code&gt;#r &amp;quot;nuget:{套件名稱}&amp;quot;&lt;/code&gt; 來安裝 Nuget 套件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此外，如果有需要指定 Nuget 套件來源，也可以使用 &lt;code&gt;#i&lt;/code&gt; 來處理&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;!c&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;i &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nuget:https://api.nuget.org/v3/index.json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;r &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nuget:Newtonsoft.Json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 需要指定版本的話，可以在後面加上版本號。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 例如：#r &amp;#34;nuget:Newtonsoft.Json, 12.0.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; Newtonsoft.Json;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; obj = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; { Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;James&amp;#34;&lt;/span&gt;, Age = &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; json = JsonConvert.SerializeObject(obj);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;json &lt;span style=&#34;color:#75715e&#34;&gt;// {&amp;#34;Name&amp;#34;:&amp;#34;James&amp;#34;,&amp;#34;Age&amp;#34;:30}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;什麼是-magic-commands&#34;&gt;什麼是 Magic Commands&lt;/h3&gt;
&lt;p&gt;在前面安裝套件的時候，我們使用了 &lt;code&gt;#&lt;/code&gt; 開頭的語法，這些語法被稱作「Magic Commands」&lt;/p&gt;
&lt;p&gt;可以參考：&lt;a href=&#34;https://github.com/dotnet/interactive/blob/main/docs/magic-commands.md&#34;&gt;Magic Commands 魔法命令 - dotnet/interactive&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;以下使用程式碼區塊（Cell）紀錄一些比較常用的 Magic Commands：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;!lsmagic
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 #!lsmagic 來列出所有支援的 Magic Commands&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 或是參考 https://github.com/dotnet/interactive/blob/main/docs/magic-commands.md&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;!csharp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 #!csharp 來指定語言為 C#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 支援的語言有：#!csharp (#!c#, #!C#), #!fsharp (#!f#, #!F#)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// #!powershell (#!pwsh), #!javascript (#!js), #!html, #!markdown&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;!time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 #!time 來計算 Cell 的執行時間&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello World!&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// Wall time: 32.5128ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 也能使用 #!mermaid 來讓 Mermaid.js 繪製流程圖&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;graph TD;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    A--&amp;gt;B;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    A--&amp;gt;C;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    B--&amp;gt;D;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    C--&amp;gt;D;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;使用-set-來跨語言傳遞變數&#34;&gt;使用 #!set 來跨語言傳遞變數&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.4.20 更新：用了一段時間之後，發現這東西的其中一個香點就在跨語言。趕緊回來補一下這段&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;當我們需要在兩個不同的程式語言之間傳遞變數，就可以使用 &lt;code&gt;#!set&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;可參考：&lt;a href=&#34;https://github.com/dotnet/interactive/blob/main/docs/variable-sharing.md&#34;&gt;Variable sharing 變量共享&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ps. &lt;code&gt;#!share&lt;/code&gt; 誕生的比較早，但後來推出了更香的 &lt;code&gt;#!set&lt;/code&gt;，所以這邊就只記 set 嚕 XD&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;!c&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fruitPrices = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;apple&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;banana&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cherry&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 同一個語言不需要特別傳遞也抓得到，所以這邊 Csharp 傳給 Csharp 不用做什麼處理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;!c&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fruitPricesInCs = JsonConvert.SerializeObject(fruitPrices);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Console.WriteLine(fruitPricesInCs); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// {&amp;#34;apple&amp;#34;:10,&amp;#34;banana&amp;#34;:20,&amp;#34;cherry&amp;#34;:30}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 現在讓我們到 JavaScript，這時候如果想從 Csharp 把變數拿過來，就需要用到 #!set
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;javascript&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fruitPricesInJs&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;csharp&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fruitPrices&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;fruitPricesInJs&lt;/span&gt;); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// {&amp;#34;apple&amp;#34;:10,&amp;#34;banana&amp;#34;:20,&amp;#34;cherry&amp;#34;:30}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;這篇記錄了 Polyglot Notebooks 的基本用法，並且介紹了一些我覺得很香的地方：輕便快速的驗證風格、能像 Python 的 Jupyter NoteBook 逐步對資料進行處理，又有 C# 的 Linq 能用，用這東西寫個爬蟲之類的小腳本，邊跑隨改實在是舒適的體驗。&lt;/p&gt;
&lt;p&gt;但同時因為這個工具還是 Preview 階段，被其他 IDE 養慣的我，已經習慣貼上語法之後讓 IDE 提示我 using 相關的命名空間進來了，因此像是 HttpClient 這種需要 using 的在這邊就很容易忘記，導致沒有跳自動完成提示的時候寫起來有點綁手綁腳（…說完感覺比較像是我的問題= = 我就菜）&lt;/p&gt;
&lt;p&gt;總之身為一個當年用過 Jupyter Notebooks 的使用者，還是很看好這個工具後續的發展。輕便又能按步驟執行、又跨語言、又能插 Markdown 來做文檔，怎麼想都香！&lt;/p&gt;
&lt;p&gt;&lt;del&gt;太香了太香了，是不是該拿來刷題了啊？&lt;/del&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2023.02.28 補充：&lt;br/&gt;前面提到沒有自動完成提示的問題，在搭配 &lt;strong&gt;Github Copilot&lt;/strong&gt; 之後，已經不是問題了！&lt;/p&gt;
&lt;p&gt;例如說需要 using 的時候，Github Copilot 會幫忙補：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mGmDW01.webp&#34;
  alt=&#34;Image&#34;width=&#34;819&#34; height=&#34;159&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝 Nuget 套件的時候，也會幫忙補上：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rI0bf5L.webp&#34;
  alt=&#34;Image&#34;width=&#34;807&#34; height=&#34;213&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;甚至－－&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IxaPE1U.webp&#34;
  alt=&#34;Image&#34;width=&#34;862&#34; height=&#34;803&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;原來 Polyglot Notebooks 搭配 Github Copilot 才是完全體啊！&lt;/p&gt;
&lt;p&gt;&lt;del&gt;果然還是該拿來刷&amp;hellip;&lt;/del&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://andrewlock.net/exploring-dotnet-interactive-notebooks/&#34;&gt;Exploring .NET interactive notebooks with VS Code (andrewlock.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/dotnet-interactive/&#34;&gt;用 Jupyter Notebook 寫 C# / PowerShell / JavaScript 筆記 - .NET Interactive-黑暗執行緒 (darkthread.net)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>《仁王》＆《仁王２》白金心得</title>
      <link>https://igouist.github.io/post/2023/01/nioh/</link>
      <pubDate>Thu, 26 Jan 2023 09:20:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2023/01/nioh/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/siUzJg8.webp&#34;
  alt=&#34;Image&#34;width=&#34;1081&#34; height=&#34;485&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;




&lt;img
  src=&#34;https://image.igouist.net/waxce1k.webp&#34;
  alt=&#34;Image&#34;width=&#34;1074&#34; height=&#34;486&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;終於趁著這次連假白金啦囧&lt;/p&gt;
&lt;p&gt;在法環的白金文有提到過，我真的很懶得刷一些要農、重複蒐集東西的成就。偏偏仁王和仁王２後期都在做這兩件事情，結果兩款的主線過完之後就進入無限拖延狀態&lt;/p&gt;
&lt;p&gt;尤其像仁王２的主線通關成就早在 2021/9/18 就拿到了，ＢＯＳＳ也都挑戰完一輪，但想到後面有一堆跟仁王１一樣要蒐集的東西，就一路拖到現在 2023/1/24 才農出白金&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/wXNOUE7.webp&#34;
  alt=&#34;Image&#34;width=&#34;940&#34; height=&#34;82&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;仁王系列給我的感覺很特別，它&lt;strong&gt;在前期的時候是個完完全全的魂系遊戲&lt;/strong&gt;：容易死亡、要熟悉ＢＯＳＳ招式、要思考打法、重複挑戰和修正等等&lt;/p&gt;
&lt;p&gt;但差不多一周目過完之後，&lt;strong&gt;中後期就會開始往暗黑那種刷裝、配裝的方向走&lt;/strong&gt;。所以最佳方針就是一路打上高難度、農出可以搭配的裝、然後大功告成輾壓眾生。到這個階段已經沒有什麼魂系遊戲的味道了，樂趣變成是嘗試各種裝備的搭配和打法，完全變成另一款遊戲&lt;/p&gt;
&lt;p&gt;二代在戰鬥和農裝都有比一代更友善一點，但感覺起來就是同一條套路。尤其我有一半時間都在用忍術隱身術之類的跑酷來蒐集東西和衝進度拿更強的裝備，所以記憶點都在蠻前面艱難打ＢＯＳＳ的一周目。只能說青菜蘿蔔各有所好，如果同時喜歡魂系和配裝的朋友可能就會挺滿意的&lt;/p&gt;
&lt;p&gt;地圖設計方面還算不錯，水面下顛倒的平等院之類的有驚艷到我。不過一二代實在太多地方會摔死了！太多了！嚴島動不動地板就有洞讓你掉到水裡，然後我一個西洋水手（一代）／超強半妖（二代）就這樣溺死，搞啥？我失足溺死的次數說不定比被ＢＯＳＳ打成肉醬的次數還多&lt;/p&gt;
&lt;p&gt;總之還是成功把這兩片都封印進去了，阿彌陀佛。&lt;del&gt;所以我說那個血源上ＰＣ呢&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7F8t7uN.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>《軟實力：軟體開發人員的生存手冊》心得</title>
      <link>https://igouist.github.io/post/2022/11/soft-skills/</link>
      <pubDate>Sun, 27 Nov 2022 00:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/11/soft-skills/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BngpHxv.webp&#34;
  alt=&#34;Image&#34;width=&#34;1016&#34; height=&#34;308&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這是一本你從未見過的軟體開發書。
&lt;br/&gt;
        
      
－－《軟實力：軟體開發人員的生存手冊》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這本書其實已經借來兩年了（隔壁 &lt;a href=&#34;https://sunnyday0932.github.io/2020/%E9%96%B1%E8%AE%80%E5%BF%83%E5%BE%97_soft-skills-%E8%BB%9F%E5%AF%A6%E5%8A%9B%E8%BB%9F%E9%AB%94%E9%96%8B%E7%99%BC%E4%BA%BA%E5%93%A1%E7%9A%84%E7%94%9F%E5%AD%98%E6%89%8B%E5%86%8A/&#34;&gt;Sian&lt;/a&gt; 心得文已經發了多久，就代表我借了多久），不過因為有一個指節那麼厚，我又是這麼的懶，因此就放在書櫃中睡了兩年，直到最近才終於捧起來看。&lt;/p&gt;
&lt;p&gt;直接先說感想：驚為天人。&lt;strong&gt;這本書的內容就像是一篇接著一篇的部落格文章&lt;/strong&gt;，但每篇之間又是環環相扣的，彷彿某種連載短篇一樣，會讓人一直看下去。&lt;/p&gt;
&lt;p&gt;對於這本書的內容，我推薦有興趣的朋友、或是喜歡閱讀部落格的朋友可以自己去試閱看看（今年剛好也出了第二版）；這邊就來聊聊我覺得這本書比較特別的部份吧！&lt;/p&gt;
&lt;h2 id=&#34;神奇的內容廣度&#34;&gt;神奇的內容廣度&lt;/h2&gt;
&lt;p&gt;首先一定要從這本書神奇的內容廣度開始說起，&lt;strong&gt;從常見的職涯建議、自我行銷，到健身、理財都有。完全對得起「生存手冊」的名稱&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;為了讓各位可以感受一下這個衝擊，這邊就條列出本書的章節目錄，並且附個一兩項裡面提到的內容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;職涯&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;如何把職涯當作企業經營？如何成為自由工作者？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自我行銷&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;如何打造個人品牌？為什麼幫助別人如此重要？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;學習&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;什麼是十步學習法？指導別人有什麼好處？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生產力&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;什麼是番茄工作法？為什麼培養習慣這麼重要？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理財&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;什麼是選擇權？如何投資房地產？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;健身&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;為什麼工程師需要健身？怎麼計算卡路里？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;心靈&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;什麼是自我形象？我們該如何樹立理想的形象？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;從目錄就可以看出來，這本書能夠給軟體工程師們為了生存所接觸到各個領域的簡介、讓讀者能快速對這些領域都有基本的概念。藉由這些基本概念，就可以根據這些方向來做進一步的了解。&lt;/p&gt;
&lt;p&gt;例如我們在生產力的章節看到「番茄工作法」，也許就可以延伸閱讀&lt;a href=&#34;https://www.books.com.tw/products/0010844785&#34;&gt;介紹番茄工作法的書籍&lt;/a&gt;；而培養習慣和自我形象的章節，也能延伸到《&lt;a href=&#34;https://www.books.com.tw/products/0010822522&#34;&gt;原子習慣&lt;/a&gt;》。諸如此類，這本書可以說是非常好的指引。&lt;/p&gt;
&lt;p&gt;先前看到 &lt;a href=&#34;https://medium.com/pm%E7%9A%84%E7%94%9F%E7%94%A2%E5%8A%9B%E5%B7%A5%E5%85%B7%E7%AE%B1/%E5%A6%82%E4%BD%95%E5%88%86%E9%A1%9E%E7%AD%86%E8%A8%98-e25c4cc39dba&#34;&gt;PARA&lt;/a&gt; 這套筆記分類法的時候，在 Area（領域）這個階段曾經困惑過：雖然說是要把寫好的筆記分類到長時間關注的各個領域主題，但&lt;strong&gt;我應該關心哪些領域才好呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;當時只能切出幾個很模糊的大領域，然後筆記還是一股腦塞進去。面對像我這種迷惘的孩子，&lt;strong&gt;這本《軟實力》完美地給出了建議：身為一個軟體工程師，我推薦你關注這些領域！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;看完的時候不經覺得，如果我在規劃筆記系統的時候就已經讀過這本書就好了！&lt;/p&gt;
&lt;h2 id=&#34;貼心的小挑戰&#34;&gt;貼心的小挑戰&lt;/h2&gt;
&lt;p&gt;書中有許多需要親身實踐的建議，在這些章節結束時都會&lt;strong&gt;給讀者一些小挑戰&lt;/strong&gt;，讓讀者能從小地方開始動手做起。&lt;/p&gt;
&lt;p&gt;例如說對自己生產力感到焦慮的我，就覺得書中對罪惡感的詮釋很有感觸：「你就是不知道自己做了多少事情，才會總覺得自己應該做更多事情」&lt;/p&gt;
&lt;p&gt;而針對這個焦慮，作者提出了番茄鐘的解法，也邀請讀者親身力行。&lt;/p&gt;
&lt;p&gt;我目前也正在嘗試使用番茄鐘來追蹤我的工作狀況。儘管結果還是有些令人慚愧，但至少是有統計資料佐證的慚愧，感覺還不錯。&lt;/p&gt;
&lt;p&gt;因此針對書中的提議，的確是可以嘗試看看。如果能像開頭提到的朋朋 &lt;a href=&#34;https://sunnyday0932.github.io/2020/%E9%96%B1%E8%AE%80%E5%BF%83%E5%BE%97_soft-skills-%E8%BB%9F%E5%AF%A6%E5%8A%9B%E8%BB%9F%E9%AB%94%E9%96%8B%E7%99%BC%E4%BA%BA%E5%93%A1%E7%9A%84%E7%94%9F%E5%AD%98%E6%89%8B%E5%86%8A/&#34;&gt;Sian&lt;/a&gt; 堅持每個小節的建議都嘗試看看，相信是絕對會有所收穫的。&lt;/p&gt;
&lt;h2 id=&#34;生產力自我推銷健身是為了讓我們成為更好的人&#34;&gt;生產力、自我推銷、健身－－是為了讓我們成為更好的人&lt;/h2&gt;
&lt;p&gt;最後來聊聊為什麼作者會在一本寫給軟體工程師的書本中，放入像是健身、理財這些主題：「為了讓我們成為更好的人」&lt;/p&gt;
&lt;p&gt;也因為作者的這個目標，在閱讀的過程中會時常有種和作者對話的感覺，隨著作者的腳步去閱覽各個能讓我們「變得更好」的主題。&lt;/p&gt;
&lt;p&gt;例如書中在講述為什麼我們害怕失敗、並鼓勵不要害怕像個傻瓜時，就像是作者直接給我建議一樣。&lt;/p&gt;
&lt;p&gt;在理財健身這類我較不熟悉的領域，就像是作者在介紹基礎知識給我這個小白；而在閱讀習慣的養成、十步學習法等等章節，又能讓我和先前閱讀的原子習慣、超速學習等知識點連結起來。&lt;/p&gt;
&lt;p&gt;因此這本書比起各種講述特定主題的書籍而言，&lt;strong&gt;更像是一本指南、一張粗略的地圖&lt;/strong&gt;，指出在我們的專業以外，可以深入研究的區域，讓我們能夠有個方向來探索。&lt;/p&gt;
&lt;p&gt;如果你也是像我一樣曾煩惱過副技能都該點些什麼，又或只是有點好奇、想找點各領域的入門磚來讀讀，相信這本書能夠讓你獲益。&lt;/p&gt;
&lt;p&gt;最後放上我在這本書裡很喜歡的一句話，和大家共勉之&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你不征服自己，你就會被自己征服。
&lt;br/&gt;
        
        
－－拿破崙·希爾&lt;/p&gt;&lt;/blockquote&gt;</description>
    </item>
    
    <item>
      <title>使用 JMeter 來對 API 壓力測試吧</title>
      <link>https://igouist.github.io/post/2022/10/jmeter/</link>
      <pubDate>Mon, 10 Oct 2022 18:50:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/10/jmeter/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/sMw5vbw.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;973&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;前陣子協助某支 API 的壓力測試，趁機請 QA 朋朋指導指導一下，因此接觸了這款簡單好用的壓測工具 JMeter，趁現在記憶還在的時候記錄起來。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;往後其他同事只要說 API 弄好了可以串了，就先幫他打個一萬次壓壓驚，真是貼心&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://jmeter.apache.org/&#34;&gt;JMeter&lt;/a&gt; 是款充滿暴力的壓力測試工具，只要告訴他：你要揍哪支 API？要揍幾拳？揍他個幾輪？它就會忠實地對你指定的 API 爆打一頓。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#下載與開啟-jmeter&#34;&gt;下載與開啟 JMeter&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#更改語言&#34;&gt;更改語言&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#批次呼叫指定的-api&#34;&gt;批次呼叫指定的 API&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#建立-thread-group&#34;&gt;建立 Thread Group&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#建立-http-request&#34;&gt;建立 Http Request&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#補充設定-http-request-header&#34;&gt;補充：設定 Http Request Header&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#建立-listener&#34;&gt;建立 Listener&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#開始執行測試&#34;&gt;開始執行測試&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#相關筆記&#34;&gt;相關筆記&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料與延伸閱讀&#34;&gt;參考資料與延伸閱讀&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;下載與開啟-jmeter&#34;&gt;下載與開啟 JMeter&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;JMeter 會需要 Java，如果還沒有 Java 的朋友可以先安裝一下：&lt;a href=&#34;https://www.java.com/zh-TW/download/ie_manual.jsp?locale=zh_TW&#34;&gt;下載 Windows 適用的 Java&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先讓我們到 JMeter 官網的 &lt;a href=&#34;https://jmeter.apache.org/download_jmeter.cgi&#34;&gt;Download 頁面&lt;/a&gt; 來下載，這邊我選擇 zip：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mBnMtJj.webp&#34;
  alt=&#34;Image&#34;width=&#34;639&#34; height=&#34;234&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;下載並解壓縮後，可以在 &lt;code&gt;/bin&lt;/code&gt; 找到 &lt;code&gt;jmeter.bat&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/N2WX2Ff.webp&#34;
  alt=&#34;Image&#34;width=&#34;699&#34; height=&#34;254&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;點開之後就會開啟 JMeter 的介面囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/d38R9nN.webp&#34;
  alt=&#34;Image&#34;width=&#34;1522&#34; height=&#34;857&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;更改語言&#34;&gt;更改語言&lt;/h3&gt;
&lt;p&gt;雖然 JMeter 的中文翻譯有些殘缺，但對英文弱弱的我來說，介面有太多英文會讓心理壓力變得太大，果然還是得先更改語言：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gTPOjr5.webp&#34;
  alt=&#34;Image&#34;width=&#34;1520&#34; height=&#34;825&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就會看到介面的大部分都變成中文囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qdtnfqJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;1520&#34; height=&#34;825&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;批次呼叫指定的-api&#34;&gt;批次呼叫指定的 API&lt;/h2&gt;
&lt;p&gt;雖然 JMeter 支援了一堆測試功能，例如 TCP 之類的，但身為一個 CRUD 工程師，我們當然要用呼叫 API 來當作範例囉！&lt;/p&gt;
&lt;p&gt;在先前的 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi/&#34;&gt;API 筆記&lt;/a&gt;，我們已經建立過簡單的「查詢卡片」API：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;GET https://localhost:44304/card
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這次就用 Local 的這支 API 作為範例來操作 JMeter 吧。&lt;/p&gt;
&lt;h3 id=&#34;建立-thread-group&#34;&gt;建立 Thread Group&lt;/h3&gt;
&lt;p&gt;讓我們回到 JMeter，可以看到預設已經有一組測試計畫了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bvkLSBy.webp&#34;
  alt=&#34;Image&#34;width=&#34;629&#34; height=&#34;261&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而我們將會在測試計畫中加入幾個元素，利用這些元素的組合來達到我們測試 API 的目標。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;首先我們會需要一個執行緒群組（Thread Group）&lt;/strong&gt;，用來告訴 JMeter 我們打算如何規劃這些 Thread 的行動。可以當作我們正在寫一個迴圈，現在才正要訂迴圈的內容而已。&lt;/p&gt;
&lt;p&gt;讓我們在預設的測試計畫上按右鍵，新增一個執行緒群組：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/iheogno.webp&#34;
  alt=&#34;Image&#34;width=&#34;624&#34; height=&#34;257&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著應該就會看到我們的測試計畫下多了一個群組，並且進入了該群組的設定畫面：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MOvw1JB.webp&#34;
  alt=&#34;Image&#34;width=&#34;1044&#34; height=&#34;529&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊要特別注意的是右側的「執行緒屬性」，我們白話一點來說明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;執行緒數量：要打幾次&lt;/li&gt;
&lt;li&gt;啟動延遲：幾秒內要打出去&lt;/li&gt;
&lt;li&gt;迴圈次數：要打幾輪&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如說，我們想要模擬一百個使用者，在一秒內湧進網站，然後這狀況持續三輪，那就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;執行緒數量 = 100&lt;/li&gt;
&lt;li&gt;啟動延遲 = 1&lt;/li&gt;
&lt;li&gt;迴圈次數 = 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這時候也就決定了這次測試的總數（執行緒數量 * 迴圈次數）&lt;/p&gt;
&lt;p&gt;此外，迴圈次數下的「Same user on each iteration」會讓每次迴圈時都用同一批使用者，如果想要每次都模擬全新的使用者，例如把前面的例子改成三百名使用者，分三批湧進網站，這時候就可以取消勾選。&lt;/p&gt;
&lt;p&gt;而定時器則是讓我們設定在執行前先延遲多久、執行多久後自動停止，這邊例子不會用到，就不開啟了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我們也可以設定 Throughput 來調整打出去的頻率，有興趣的朋友請參考 &lt;a href=&#34;https://igouist.github.io/post/2025/02/jmeter-constant-throughput-timer&#34;&gt;JMeter: 使用 Constant Throughput Timer 設置固定吞吐量&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在我們已經把執行緒群組建好了，讓我們指派任務給這個群組吧！&lt;/p&gt;
&lt;h3 id=&#34;建立-http-request&#34;&gt;建立 Http Request&lt;/h3&gt;
&lt;p&gt;在執行緒群組上右鍵 &amp;gt; 新增，可以看到一排能指派給這群組的任務，其中「取樣」就是指我們這次的目標。這邊讓我們選擇「取樣 &amp;gt; HTTP 要求」：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/a2Qsovz.webp&#34;
  alt=&#34;Image&#34;width=&#34;674&#34; height=&#34;551&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著可以看到 HTTP Request 的設定畫面，如同前面所說的，我們這次的目標是 &lt;code&gt;GET https://localhost:44304/card&lt;/code&gt;，因此將 API 的資訊填到對應的欄位中：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Vm2tW4M.webp&#34;
  alt=&#34;Image&#34;width=&#34;1727&#34; height=&#34;419&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在 API 的路由等資訊填好後，下方可以設定呼叫時要帶的參數，例如 Body 或上傳檔案等等。&lt;/p&gt;
&lt;h3 id=&#34;補充設定-http-request-header&#34;&gt;補充：設定 Http Request Header&lt;/h3&gt;
&lt;p&gt;很多時候我們要測試的 API 會要求在 Header 帶著 Token 才能使用，這邊也記錄一下掛 Header 的做法。&lt;/p&gt;
&lt;p&gt;對執行緒群組右鍵 &amp;gt; 新增，選擇「設定元素」，裡面就會有一票設定值可以選，這邊選擇「HTTP 標頭管理員」：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HYbxiCs.webp&#34;
  alt=&#34;Image&#34;width=&#34;726&#34; height=&#34;664&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;例如我們要指定 Post 的 Body 為 Json，就可以掛一個 &lt;code&gt;content-type&lt;/code&gt; = &lt;code&gt;application/json&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IAPpOCu.webp&#34;
  alt=&#34;Image&#34;width=&#34;320&#34; height=&#34;170&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就會加到 Header 上囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6Df2gvq.webp&#34;
  alt=&#34;Image&#34;width=&#34;627&#34; height=&#34;182&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;不過我們這次的範例只是一支簡單的查詢卡片，這邊就不用加上啦。讓我們進行下一步吧！&lt;/p&gt;
&lt;h3 id=&#34;建立-listener&#34;&gt;建立 Listener&lt;/h3&gt;
&lt;p&gt;當我們的小弟們去解決了目標之後，就必須要回來報告給我們知道。這邊讓我們一樣在執行緒群組上右鍵新增，並且選擇「接聽（Listener）」，先嘗試建立一個「檢視結果樹」：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PsNUHkJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;724&#34; height=&#34;601&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了檢視結果樹以外也可以新增別的試試看，方便打完 API 之後確認，這邊再加上一個「Summary Report」，現在群組應該會長得像這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/LVfx6r7.webp&#34;
  alt=&#34;Image&#34;width=&#34;194&#34; height=&#34;112&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;等等打完之後再來確認這兩頁的內容。現在讓我們戳戳看 API 吧！&lt;/p&gt;
&lt;h3 id=&#34;開始執行測試&#34;&gt;開始執行測試&lt;/h3&gt;
&lt;p&gt;在最上面找到大家都很熟悉的綠色三角，戳下去就會執行這次的測試計畫，也就是照著剛剛的計畫烙一群小弟去揍 API&lt;/p&gt;
&lt;p&gt;它旁邊還有發現不對勁時使用的 STOP，更旁邊的神奇掃把是用來在結束之後滅證用的清除結果：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UHKKu0W.webp&#34;
  alt=&#34;Image&#34;width=&#34;628&#34; height=&#34;157&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;總之，用力給他按下綠色三角！&lt;/p&gt;
&lt;p&gt;這邊就先意思意思揍個一拳，執行之後就可以來看看我們的檢視結果樹：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rNVpHQD.webp&#34;
  alt=&#34;Image&#34;width=&#34;747&#34; height=&#34;581&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到結果樹中會顯示剛剛執行的結果，假設你打了兩百次這邊就會有兩百筆；右側則是這次 API 回傳的結果，通常都是看有沒有好好回 200 OK 就是了 xD&lt;/p&gt;
&lt;p&gt;此外也可以切換上面的頁籤，看這次 API 回傳的內容：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OEkzdit.webp&#34;
  alt=&#34;Image&#34;width=&#34;720&#34; height=&#34;175&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著切換到剛剛新增過的 Summary Report，可以看 API 回來的時間等資訊：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BhUNjbc.webp&#34;
  alt=&#34;Image&#34;width=&#34;1256&#34; height=&#34;336&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就完成一次測試啦！&lt;/p&gt;
&lt;p&gt;為了慶祝完成來打個一萬次 API 先（？）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DX6uzQW.webp&#34;
  alt=&#34;Image&#34;width=&#34;788&#34; height=&#34;85&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;這次筆記了 JMeter 這套壓力測試工具的簡單使用方式，在大多數對 API 做測試的場合中，只需要新增幾個 JMeter 提供的元件就可以完成任務，可以說是相當方便。&lt;/p&gt;
&lt;p&gt;同時 JMeter 也提供了更多進階場景時用到的工具，來幫助我們在 Request 發送前後進行處理：例如在這次工作上用到的場景來說，就需要從設定元素中新增 CSV Data Set Config 來從 CSV 讀出需要的資訊，利用 &lt;code&gt;${參數名稱}&lt;/code&gt; 的語法來把參數加到我們的 HTTP 請求：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/442wtcA.webp&#34;
  alt=&#34;Image&#34;width=&#34;1141&#34; height=&#34;616&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/xwrJur8.webp&#34;
  alt=&#34;Image&#34;width=&#34;582&#34; height=&#34;164&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6i8kVCO.webp&#34;
  alt=&#34;Image&#34;width=&#34;492&#34; height=&#34;117&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;又或是新增前置處理器，如 JSR223 PreProcesser 來撰寫 JavaScript 腳本，先對參數進行處理等等：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Dv3eiya.webp&#34;
  alt=&#34;Image&#34;width=&#34;683&#34; height=&#34;445&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UZFyHTf.webp&#34;
  alt=&#34;Image&#34;width=&#34;492&#34; height=&#34;112&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當然除了從 CSV 設定參數以外，JMeter 也提供了簡單的邏輯控制，像是從一數到十這種是完全沒問題的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Mdwio69.webp&#34;
  alt=&#34;Image&#34;width=&#34;548&#34; height=&#34;419&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有這些工具可以進行組合，可以說是相當方便。但大多時候我們只會需要用到基本的 Http Request。有興趣嘗試的朋友可以參考 Yuanchieh&amp;rsquo;s Blog 的這篇：&lt;a href=&#34;https://yuanchieh.page/posts/2021/2021-06-26-jmeter-%E4%BD%BF%E7%94%A8%E6%95%99%E5%AD%B8-+-%E8%87%AA%E5%AE%9A%E7%BE%A9%E8%AE%8A%E6%95%B8%E4%BD%BF%E7%94%A8/&#34;&gt;壓測工具：JMeter 使用教學 + 自定義變數使用&lt;/a&gt;，以及官方文件 &lt;a href=&#34;https://jmeter.apache.org/usermanual/index.html&#34;&gt;Apache JMeter - User&amp;rsquo;s Manual&lt;/a&gt; 來試試，這邊就不再贅述。&lt;/p&gt;
&lt;p&gt;雖然 JMeter 還是有一點小問題，例如那個只有一半的翻譯，還有&lt;a href=&#34;https://stackoverflow.com/questions/53502142/apache-jmeter-showing-different-result-in-windows-and-linux-for-same-test&#34;&gt;跑在 Linux 的表現會比 Windows 好&lt;/a&gt;之類的現象&lt;/p&gt;
&lt;p&gt;但這無損它是個功能完善又簡單使用的壓力測試工具。需要的時候還是可以打開 JMeter，簡單掛個執行緒群組就揍起來，同事 API 寫好就直接把它打爛，不亦樂乎。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;雖然我的後端前輩最後跑去用更新更潮的 &lt;a href=&#34;https://k6.io/&#34;&gt;K6&lt;/a&gt; 了，嘖。我筆記都寫完了欸，讓我看看是有多潮……&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/81n6ddE.webp&#34;
  alt=&#34;Image&#34;width=&#34;461&#34; height=&#34;336&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;那麼今天的筆記就到這邊，我們下次見！&lt;/p&gt;
&lt;h2 id=&#34;相關筆記&#34;&gt;相關筆記&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2025/02/jmeter-constant-throughput-timer&#34;&gt;JMeter: 使用 Constant Throughput Timer 設置固定吞吐量&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料與延伸閱讀&#34;&gt;參考資料與延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ＱＡ同事的工作坊&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lufor129.medium.com/%E6%B8%AC%E5%A5%BD%E6%B8%AC%E6%BB%BF-%E4%B8%80-%E5%A3%93%E5%8A%9B%E6%B8%AC%E8%A9%A6jmeter-5356b5335628&#34;&gt;測好測滿(一) : 壓力測試Jmeter | by LUFOR129 | Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10203900?sc=hot&#34;&gt;Day 20 Jmeter 壓力測試工具 - iT 邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://yuanchieh.page/posts/2021/2021-06-26-jmeter-%E4%BD%BF%E7%94%A8%E6%95%99%E5%AD%B8-+-%E8%87%AA%E5%AE%9A%E7%BE%A9%E8%AE%8A%E6%95%B8%E4%BD%BF%E7%94%A8/&#34;&gt;壓測工具：JMeter 使用教學 + 自定義變數使用 | Yuanchieh&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2022/10/15/load_stress_test_tool_for_jmeter&#34;&gt;負載壓力測試工具 - JMeter - 余小章 @ 大內殿堂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.astralweb.com.tw/jmeter-website-stress-testing-tutorial/&#34;&gt;JMeter-網頁壓力測試教學 - Astral Web 歐斯瑞有限公司&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/105285382&#34;&gt;jmeter 撒的一个谎，你可能都已经信以为真了 - 知乎 (zhihu.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jmeter.apache.org/usermanual/index.html&#34;&gt;Apache JMeter - User&amp;rsquo;s Manual&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Sony WH-1000XM5 使用一週心得</title>
      <link>https://igouist.github.io/post/2022/10/sony-wh-1000xm5/</link>
      <pubDate>Sat, 01 Oct 2022 20:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/10/sony-wh-1000xm5/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rixivwP.webp&#34;
  alt=&#34;Image&#34;width=&#34;1080&#34; height=&#34;1153&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在某個風和日麗的午後，同事讓我試戴他的 XM5，當我戴上去聽了兩秒音樂，&lt;strong&gt;就明白我已經犯了錯&lt;/strong&gt;，而且無法回頭。&lt;/p&gt;
&lt;p&gt;過了幾天，這副耳機就出現在我家門口，到今天已經過了一週，就讓我來紀錄一下小小心得吧。&lt;/p&gt;
&lt;h3 id=&#34;音質升級降噪有感&#34;&gt;音質升級、降噪有感&lt;/h3&gt;
&lt;p&gt;我的上一副耳罩耳機是陪了我好多年、用到脫皮的 &lt;a href=&#34;https://www.avantree.com.tw/products/audition-pro&#34;&gt;Avantree Audition Pro&lt;/a&gt;，因此整個升級感還蠻明顯的。&lt;/p&gt;
&lt;p&gt;其中最有感的就是音質和降噪兩個部份。身為一個木耳仔，音質的升級也只能說出「&lt;strong&gt;哦哦！這個…比較厲害！&lt;/strong&gt;」的心得，這邊就不獻醜了，總之我是聽得一個開心。&lt;/p&gt;
&lt;p&gt;至於降噪的部份就真的是無線耳機第一梯隊，我所在的辦公室相當開放，同事間也經常討論，並且在入手耳機時還正逢隔壁施工。但當降噪開下去，真的有種隔離感，瞬間就只剩下音樂陪伴，令人相當滿意，貝多芬試了也會微笑。&lt;/p&gt;
&lt;p&gt;通透的表現也很不錯，左側有實體按鍵能切換降噪／通透，使用上相當直覺。但因為習慣問題，說話時還是會把耳機摘下來，這時候會自動暫停音樂，還算貼心。&lt;/p&gt;
&lt;p&gt;此外，APP 也有提供等化器調整，切換風格的感覺蠻明顯的，不過像我這種門外漢也就是調個心情愉快就放著了。到目前 APP 沒開過幾次，比較像是個徽章蒐集器：「恭喜你又多聽了一天！」&lt;/p&gt;
&lt;h3 id=&#34;舒適好戴都是汗&#34;&gt;舒適、好戴、都是汗&lt;/h3&gt;
&lt;p&gt;聊完我覺得有感升級的部份，現在聊聊另一個同樣有感的部份：熱。&lt;/p&gt;
&lt;p&gt;第一天拿到的時候興高采烈聽了一兩小時的歌，實在是相當舒適，耳朵和眼鏡都沒有什麼壓迫感，頭頂拉開之後也不夾頭，完全可以佩戴上好一陣子沒問題。&lt;/p&gt;
&lt;p&gt;但一拿下來發現&lt;strong&gt;耳罩上整圈的汗&lt;/strong&gt;，才發現事情不大對勁，耳朵彷彿洗了三溫暖。&lt;/p&gt;
&lt;p&gt;剎那間我就明白，這東西是不能在我家這種冷氣很爛的環境下使用的，非得要到冷得要死的辦公室，那保暖起來才剛剛好。&lt;/p&gt;
&lt;p&gt;除了熱以外，攜帶性也讓人不大習慣，畢竟這代無法摺疊，而耳機本體和收納包都挺大一個的，側背包根本放不下，要帶著走，乾脆直接戴在頭上。&lt;/p&gt;
&lt;h3 id=&#34;小結&#34;&gt;小結&lt;/h3&gt;
&lt;p&gt;一句話總結：音質和降噪讓我這木耳很滿意，但又熱又大，想入手的朋朋呀，使用場景得好好考慮。&lt;/p&gt;
&lt;p&gt;題外話，上一次介紹這些小東西是前年買&lt;a href=&#34;https://igouist.github.io/post/2020/10/external-screen/&#34;&gt;外接螢幕&lt;/a&gt;的事兒了。那為什麼突然想寫這篇呢，原因非常簡單：&lt;/p&gt;
&lt;p&gt;看到進版圖了嗎？這是我家的豬，我覺得超可愛，如果你沒看過，現在讓你看看。&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>在 Chrome 使用翻譯套件 ImTranslator 的嵌入式翻譯來逐行中英對照吧</title>
      <link>https://igouist.github.io/post/2022/09/imtranslator/</link>
      <pubDate>Sun, 18 Sep 2022 10:55:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/09/imtranslator/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/0XXKTrd.webp&#34;
  alt=&#34;Image&#34;width=&#34;746&#34; height=&#34;233&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;嗨各位朋朋，又到了替文章數灌水的好用工具時間！&lt;/p&gt;
&lt;p&gt;今天要介紹的是 &lt;a href=&#34;https://chrome.google.com/webstore/detail/imtranslator-translator-d/noaijdpnepcgjemiklgfkcfbkokogabh?hl=zh-TW&#34;&gt;ImTranslator&lt;/a&gt;，它是一款 Chrome 的翻譯套件。由於有&lt;strong&gt;嵌入式翻譯（Inline Translator）&lt;/strong&gt;，意外地在辦公室的詢問度還蠻高的，這就來寫一篇介紹來推廣給和我一樣英文苦手的朋朋們！&lt;/p&gt;
&lt;p&gt;首先讓我們到 &lt;a href=&#34;https://chrome.google.com/webstore/detail/imtranslator-translator-d/noaijdpnepcgjemiklgfkcfbkokogabh?hl=zh-TW&#34;&gt;Chrome 線上應用程式商店的 ImTranslator 頁面&lt;/a&gt; 將套件安裝到瀏覽器中：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/v8ADxhi.webp&#34;
  alt=&#34;Image&#34;width=&#34;553&#34; height=&#34;261&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝完之後預設會是翻譯成世界語，因此我們要先到擴充功能選項調整一下語言：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UekSSix.webp&#34;
  alt=&#34;Image&#34;width=&#34;318&#34; height=&#34;282&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到左邊有四種翻譯模式，我們先把翻譯語言都調整一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6WJqXzZ.webp&#34;
  alt=&#34;Image&#34;width=&#34;1077&#34; height=&#34;985&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊也可以更改翻譯來源，我個人是都用預設的 Google 翻譯。此外像是嵌入式翻譯結果的文字顏色也可以在這邊修改。&lt;/p&gt;
&lt;p&gt;接著我們來看看這四種翻譯模式吧：&lt;/p&gt;
&lt;p&gt;首先是我個人最常用的嵌入式翻譯（Inline Translator），只要將翻譯目標的句子反白起來，按下 &lt;strong&gt;Alt + C&lt;/strong&gt;，就會在翻譯目標的句子後方插入翻譯結果：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bE49bME.webp&#34;
  alt=&#34;Image&#34;width=&#34;728&#34; height=&#34;225&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果想清除翻譯結果可以按下 Alt + X：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ULRFWJd.webp&#34;
  alt=&#34;Image&#34;width=&#34;736&#34; height=&#34;207&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著是彈出視窗（Pop-up Bubble），顧名思義就是彈出式視窗。不過由於我不喜歡彈一塊視窗出來擋畫面，平常都是關閉的，只有嵌入式翻譯會讓版面跑版的時候才從右鍵選單使用：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/llDpDnC.webp&#34;
  alt=&#34;Image&#34;width=&#34;733&#34; height=&#34;479&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著是窗口翻譯（ImTranslator），就是彈出視窗的獨立視窗版，好處是可以用滑鼠拖移：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vNKPRY4.webp&#34;
  alt=&#34;Image&#34;width=&#34;479&#34; height=&#34;508&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最後是網頁翻譯（Webpage Translation），不過這功能就和 Chrome 右鍵的「翻譯成 繁體（中文）」意思一樣，就不再贅述，我也沒用過。&lt;/p&gt;
&lt;p&gt;看過一輪上面的翻譯模式後，應該可以發現這套件最香的還是嵌入式翻譯，可以中英對照對我這種不擅長英文又怕被機器翻譯幹掉術語的人來說真是一大救贖，善哉善哉。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;我相信真的有人用中英對照來練習英文閱讀啦，但我是真的看不懂= =a&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;這邊就推薦給各位和我一樣的朋朋，省卻複製貼上到 Google 翻譯的時間吧！&lt;/p&gt;
&lt;h2 id=&#34;參考資料推坑來源&#34;&gt;參考資料（推坑來源）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2017/12/imtranslator-firefox.html&#34;&gt;ImTranslator 我選擇的 Firefox 即時翻譯套件，好看順手多功能 (playpcesor.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>使用 Azure Functions &#43; Line Notify 來定時提醒公車到站時間</title>
      <link>https://igouist.github.io/post/2022/09/bus-reminder-2-azure-functions-timetrigger-with-line-notify/</link>
      <pubDate>Mon, 12 Sep 2022 23:51:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/09/bus-reminder-2-azure-functions-timetrigger-with-line-notify/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WX17auT.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;1014&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在上週的 &lt;a href=&#34;https://igouist.github.io/post/2022/09/bus-reminder-1-powershell-and-windows-task-scheduler&#34;&gt;使用 Powershell + 工作排程器 + Line Notify 來定時提醒公車到站時間&lt;/a&gt;，我們利用工作排程器來定時觸發腳本，藉此用 Line 提醒我下班的公車還有多久才來。&lt;/p&gt;
&lt;p&gt;做完之後靈機一動，對呀！最近上班挺常接觸到 &lt;a href=&#34;https://azure.microsoft.com/zh-tw/services/functions/&#34;&gt;Azure Functions&lt;/a&gt; 這個方便東東，不如就把這個小提醒給架設到 Azure Functions 上吧！&lt;/p&gt;
&lt;p&gt;這樣就省卻了特定主機要開著掛工作排程器的困擾，又可以用香香的 Azure 工具來控制監聽的開關，豈不美哉。&lt;/p&gt;
&lt;p&gt;如此如此這般這般，讓我們開始建立 Azure Functions 服務吧！&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#建立-azure-functions-資源&#34;&gt;建立 Azure Functions 資源&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-azure-functions-開發公車到站提醒服務&#34;&gt;使用 Azure Functions 開發公車到站提醒服務&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#建立-azure-funtcions-專案&#34;&gt;建立 Azure Funtcions 專案&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#呼叫-motc-api-查詢公車到站預估時間&#34;&gt;呼叫 MOTC Api 查詢公車到站預估時間&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#發送-line-notify&#34;&gt;發送 Line Notify&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#組合查詢公車資訊與發送到站通知&#34;&gt;組合查詢公車資訊與發送到站通知&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#本機測試-functions&#34;&gt;本機測試 Functions&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-visual-studio-發佈到-azure-functions&#34;&gt;使用 Visual Studio 發佈到 Azure Functions&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;建立-azure-functions-資源&#34;&gt;建立 Azure Functions 資源&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Azure Functions 是 Azure 推出的一款無伺服器（Serverless）服務，簡單來說就是伺服器之類的麻煩事就交給 Azure 去處理，我們只要專心寫功能就好&lt;/strong&gt;。對我這種愛寫小腳本的偷懶工程師來說，可以說是香到爆的服務。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提醒：Azure Functions 是一款收費服務，使用前請務必確認&lt;a href=&#34;https://azure.microsoft.com/zh-tw/pricing/details/functions/&#34;&gt;定價&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;在這篇文章撰寫當下，Azure Functions 有提供每月免費執行一百萬次的授權，對我們每天一次的公車通知來說綽綽有餘了（我們應該不會搭這麼多趟吧…？）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先讓我們到 &lt;a href=&#34;https://portal.azure.com/#home&#34;&gt;Azure&lt;/a&gt; 建立一個函數應用程式（如果用英文，請找 Azure Functions）：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9SJklpx.webp&#34;
  alt=&#34;Image&#34;width=&#34;444&#34; height=&#34;165&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/emG0G65.webp&#34;
  alt=&#34;Image&#34;width=&#34;891&#34; height=&#34;357&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著進到建立 Functions 的頁面，讓我們先選好資源群組，並取個好名字&lt;/p&gt;
&lt;p&gt;因為「MyFunctions」之類的都被取走了，這邊就直接取「林北ㄟ Functions」：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pyi77OX.webp&#34;
  alt=&#34;Image&#34;width=&#34;743&#34; height=&#34;911&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;執行階段堆疊請選擇自己開發用的語言，我這邊使用 .Net 6 進行開發，作業系統則按照建議的選擇。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這邊要稍微注意方案的選擇！如同前面提到的&lt;a href=&#34;https://azure.microsoft.com/zh-tw/pricing/details/functions/&#34;&gt;定價&lt;/a&gt;，也可以參照 Microsoft Docs 的&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-consumption-costs?tabs=portal&#34;&gt;預估 Azure Functions 中的取用方案成本&lt;/a&gt;說明，裡面會有使用量、進階等方案的說明。&lt;/p&gt;
&lt;p&gt;這次我們要做的只是簡單的提醒通知，所以就選費用最低的使用量計價就好囉～&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;按下確認後就會開始部屬：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HAk5hdD.webp&#34;
  alt=&#34;Image&#34;width=&#34;1132&#34; height=&#34;505&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;部屬完成就可以前往我們建立的資源囉，可以在這裡確認記憶體、執行次數等資訊：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/xBbnNbB.webp&#34;
  alt=&#34;Image&#34;width=&#34;887&#34; height=&#34;314&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pHteaUf.webp&#34;
  alt=&#34;Image&#34;width=&#34;1488&#34; height=&#34;840&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;從左側的「函式」可以確認我們現在有哪些 Functions，當然目前還是空的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WWLtsFG.webp&#34;
  alt=&#34;Image&#34;width=&#34;949&#34; height=&#34;410&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就讓我們來撰寫第一個 Function 吧！&lt;/p&gt;
&lt;h2 id=&#34;使用-azure-functions-開發公車到站提醒服務&#34;&gt;使用 Azure Functions 開發公車到站提醒服務&lt;/h2&gt;
&lt;p&gt;因為這篇的功能完全是&lt;a href=&#34;https://igouist.github.io/post/2022/09/bus-reminder-1-powershell-and-windows-task-scheduler&#34;&gt;使用 Powershell + 工作排程器 + Line Notify 來定時提醒公車到站時間&lt;/a&gt;的完美復刻版，因此我們要做的事情還是一樣：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每天下班前十分鐘（定時執行）&lt;/li&gt;
&lt;li&gt;告訴我（通知功能）&lt;/li&gt;
&lt;li&gt;下一班到達的公車時間（查詢資訊）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只是這次的功能使用 .Net 6 撰寫，並且使用 Visual Studio 為範例來記錄，定時功能則從臭臭又綁電腦的工作排程器改用香香 Azure Functions 的定時觸發功能。&lt;/p&gt;
&lt;h3 id=&#34;建立-azure-funtcions-專案&#34;&gt;建立 Azure Funtcions 專案&lt;/h3&gt;
&lt;p&gt;首先讓我們新增專案，內建已經有 Azure Functions 的範例可以使用：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gqnprvJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;1008&#34; height=&#34;672&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;取個好名字，這邊沿用剛剛開資源的命名：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bJtncZx.webp&#34;
  alt=&#34;Image&#34;width=&#34;1008&#34; height=&#34;672&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就要選擇版本，這邊&lt;strong&gt;要注意 Azure Function 目前還有分出隔離版（Isolated）&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6LpLjZX.webp&#34;
  alt=&#34;Image&#34;width=&#34;1008&#34; height=&#34;672&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;簡單來說，原本的 Azure Functions 和主機環境太耦合了，如果用到同一個套件不同版本就有可能翻車。因此推出了隔離式的 Azure Functions 讓我們可以乾乾淨淨地用。&lt;/p&gt;
&lt;p&gt;想更了解隔離式版本的差異，可以參見：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/dotnet-isolated-process-guide#why-net-isolated-process&#34;&gt;在隔離式程序中執行 C# Azure Functions 的指南 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://markheath.net/post/azure-functions-isolated&#34;&gt;Is it time to start creating C# Azure Functions in isolated mode? (markheath.net)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由於 &lt;a href=&#34;https://techcommunity.microsoft.com/t5/apps-on-azure-blog/net-on-azure-functions-roadmap/ba-p/2197916&#34;&gt;.NET on Azure Functions Roadmap&lt;/a&gt; 的示意圖：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS0yMTk3OTE2LTI2MjMxOGk0MjM0QjEzMkM3NDI1MDlD?image-dimensions=999x340&amp;amp;revision=8&#34;
  alt=&#34;&#34;width=&#34;998&#34; height=&#34;340&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;明確指出將來的主軸會是隔離式（Isolated），因此我們這邊專案也選擇 .Net 6 已隔離的版本。&lt;del&gt;這樣我往後抄起來比較方便&lt;/del&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：查詢 Azure Functions 相關資料時也要注意版本的差異！&lt;strong&gt;在 .Net 開發隔離式的 SDK 並不一樣&lt;/strong&gt;，連最基本標示 Function 的語法，原本是 &lt;code&gt;[FunctionName()]&lt;/code&gt;，隔離式也改成了更簡潔的 &lt;code&gt;[Function()]&lt;/code&gt;，&lt;strong&gt;因此查資料或開發時要特別注意版本差異&lt;/strong&gt;，避免被 IDE 畫了紅線卻搞不懂為什麼。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著我們就可以選擇 Function 的觸發條件，因為我們要定時提醒，因此這邊選擇 Timer Trigger 就可以了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/eIzaYSc.webp&#34;
  alt=&#34;Image&#34;width=&#34;1008&#34; height=&#34;672&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;同樣常用的還有當成 API 打的 Http Trigger，以及和我們上一次介紹過的 &lt;a href=&#34;https://igouist.github.io/post/2022/08/azure-service-bus/&#34;&gt;ServiceBus&lt;/a&gt; 一起使用的 Service Bus Queue/Topic Trigger 等等。&lt;/p&gt;
&lt;p&gt;關於提供的觸發方式和程式碼範例，可以參照 Microsoft Docs 的 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-triggers-bindings?tabs=csharp#supported-bindings&#34;&gt;Azure Functions 中的觸發程序和繫結&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;最後因為我們選擇了 Timer Trigger，這邊提供我們直接設定時間。使用的是 NCrontab 格式，可以參考 &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=in-process&amp;amp;pivots=programming-language-csharp#cron-expressions&#34;&gt;NCRONTAB expressions&lt;/a&gt; 的說明，Visuat Studio 上也有簡短地介紹：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6OB1Qrs.webp&#34;
  alt=&#34;Image&#34;width=&#34;932&#34; height=&#34;126&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;按照需求，我們希望下班前，也就是每天的 17:45 左右能提醒公車預估到站的時間。&lt;/p&gt;
&lt;p&gt;NCrontab 和常見的 Crontab 差在多了第一個欄位來控制秒，因此這邊果斷直接使用 &lt;a href=&#34;https://crontab.guru/#*_*_*_*_*&#34;&gt;Cronitor&lt;/a&gt; 查一下，再往前加上一欄當作秒數即可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：另一個香香工具 &lt;a href=&#34;https://igouist.github.io/post/2022/08/devtoys/&#34;&gt;DevToys&lt;/a&gt; 也能迅速組裝和確認 Cron 語法呦！&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;每天的 17:45 在 Crontab 表示為「45 17 * * *」：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/o5k9Jvl.webp&#34;
  alt=&#34;Image&#34;width=&#34;1041&#34; height=&#34;259&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們希望在 0 秒的時候觸發，因此轉 NCrontab 時需要在秒的位置指定 0，也就是「0 45 17 * * *」&lt;/p&gt;
&lt;p&gt;但在填到 Azure Functions 要注意，&lt;strong&gt;在伺服器的時間會是 UTC+0&lt;/strong&gt;。為了在台灣，也就是 UTC+8 的 17:45 觸發，因此將時間更改為「0 45 9 * * *」，否則 Line 就會在半夜通知你起來搭公車&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bDNvzhE.webp&#34;
  alt=&#34;Image&#34;width=&#34;1059&#34; height=&#34;237&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣其他資訊的頁面就填寫完了，可以按下建立囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/STp7otF.webp&#34;
  alt=&#34;Image&#34;width=&#34;1008&#34; height=&#34;672&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;建立後會看到 Visual Studio 已經使用我們剛剛的設置建立了一個 Function 及 Timer Trigger，時間也填好了（如果後續還要調整時間，就修改 &lt;code&gt;TimerTrigger&lt;/code&gt; 的值就好）：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Function1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ILogger _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Function1(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ILoggerFactory loggerFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger = loggerFactory.CreateLogger&amp;lt;Function1&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Function(&amp;#34;Function1&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Run([TimerTrigger(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0 45 9 * * *&amp;#34;&lt;/span&gt;)] MyInfo myTimer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger.LogInformation(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;C# Timer trigger function executed at: {DateTime.Now}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger.LogInformation(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Next timer schedule at: {myTimer.ScheduleStatus.Next}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;為了後續管理方便，我們先改個名。這邊就叫做 &lt;code&gt;BusReminderFunctions&lt;/code&gt; 吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BusReminderFunctions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ILogger _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; BusReminderFunctions(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ILoggerFactory loggerFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger = loggerFactory.CreateLogger&amp;lt;BusReminderFunctions&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Function(&amp;#34;BusReminder-TimerTrigger&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; RunTimerTrigger([TimerTrigger(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0 45 9 * * *&amp;#34;&lt;/span&gt;)] MyInfo myTimer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger.LogInformation(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;C# Timer trigger function executed at: {DateTime.Now}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger.LogInformation(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Next timer schedule at: {myTimer.ScheduleStatus.Next}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;並且為了測試方便，我們再增加一個 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=isolated-process%2Cfunctionsv2&amp;amp;pivots=programming-language-csharp&#34;&gt;HttpTrigger&lt;/a&gt;，這時候會需要先去 Nuget 安裝相關的套件&lt;/p&gt;
&lt;p&gt;搜尋 &lt;code&gt;Microsoft.Azure.Functions.Worker.Extensions&lt;/code&gt; 就會看到各種 Trigger，這邊就安裝一下 Http 需要的套件吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/d4WDrue.webp&#34;
  alt=&#34;Image&#34;width=&#34;1002&#34; height=&#34;455&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在讓我們回到 Functions，增加一個 &lt;code&gt;HttpTrigger&lt;/code&gt;，調整一下非同步，並建立一個私有方法 &lt;code&gt;TrackBusAsync&lt;/code&gt;，讓 &lt;code&gt;HttpTrigger&lt;/code&gt; 和 &lt;code&gt;TimerTrigger&lt;/code&gt; 都去呼叫這個方法，這樣我們就可以定時觸發也可以手動觸發它，後續測試起來也比較方便：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Function(&amp;#34;BusReminder-TimerTrigger&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task RunTimerTrigger([TimerTrigger(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0 45 9 * * *&amp;#34;&lt;/span&gt;)] MyInfo myTimer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; TrackBusAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Function(&amp;#34;BusReminder&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;HttpResponseData&amp;gt; HttpTrigger(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpTrigger(AuthorizationLevel.Anonymous, &amp;#34;get&amp;#34;, &amp;#34;post&amp;#34;)]&lt;/span&gt; HttpRequestData req,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FunctionContext executionContext)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; TrackBusAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; response = req.CreateResponse(HttpStatusCode.OK);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; response.WriteAsJsonAsync&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;object&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; { result = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; response;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task TrackBusAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;補充：HttpTrigger 的參數有三個部份，可以拆分成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;授權層級：Anonymous, Admin 之類的，可參照&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=isolated-process%2Cfunctionsv2&amp;amp;pivots=programming-language-csharp#http-auth&#34;&gt;授權層級&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;方法：Get, Post 之類的，沒指定就會是不限方法&lt;/li&gt;
&lt;li&gt;路由：用來定義 API 路由，預設會拿 FunctionName，在這例子就是 &lt;code&gt;BusReminder&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;都有指定的樣子會像這樣：&lt;br/&gt;&lt;code&gt;[HttpTrigger(AuthorizationLevel.Anonymous, &amp;quot;get&amp;quot;, &amp;quot;post&amp;quot;, Route = null)]&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這樣事前準備就差不多了，讓我們開始撰寫邏輯部分吧！&lt;/p&gt;
&lt;h3 id=&#34;呼叫-motc-api-查詢公車到站預估時間&#34;&gt;呼叫 MOTC Api 查詢公車到站預估時間&lt;/h3&gt;
&lt;p&gt;首先我們需要取得公車到站預估時間，這部分跟上一期一樣就直接從公共運輸動態服務 MOTC Transport API 的「市區公車之預估到站資料」（&lt;code&gt;EstimatedTimeOfArrival/City/{City}/{RouteName}&lt;/code&gt;） 這支 API 取得就行了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.11.25 更新：發現 MOTC 的 API 已經下架了，現在公車資訊已經被整合到運輸資料服務 &lt;a href=&#34;https://tdx.transportdata.tw/api-service/swagger/basic/2998e851-81d0-40f5-b26d-77e2f5ac4118#/&#34;&gt;TDX (Transport Data eXchange)&lt;/a&gt;，有要動手串公車資訊的朋友可能得觀察新版 API 再進行調整了 QQ&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;註：為了避免被查水表，以下就用台北的 307 號公車為例，並假設目標站點是「台北車站（忠孝）」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;嘗試把縣市和公車路線名稱代入後，可以取得公車行經的站牌資訊，其中就有我們最想要的估計到站時間（秒）：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GQ9wdqG.webp&#34;
  alt=&#34;Image&#34;width=&#34;1000&#34; height=&#34;586&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在這個步驟我們還會需要調整 &lt;code&gt;$top&lt;/code&gt; 參數的筆數，順便取得我們要監聽公車到站的站牌 ID（StopId）以及行進方向（Direction） 在這個例子中「台北車站（忠孝）」的站牌 ID 會是 15250&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mixE42V.webp&#34;
  alt=&#34;Image&#34;width=&#34;994&#34; height=&#34;576&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;確保了資料來源之後，讓我們回到專案裡。&lt;/p&gt;
&lt;p&gt;這次為了之後方便擴展，決定將公車名稱、站牌 ID 等查詢資訊放到組態裡。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：接下來會使用到 &lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/&#34;&gt;依賴注入&lt;/a&gt; 以及讀取 Config 的 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0&#34;&gt;IOptions&lt;/a&gt;；對這兩項不太熟悉的朋友可以先看過去，並且在後續 BusReminderFunctions 的建構式裡把公車資訊寫死就好，並不會影響功能。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;一般的 .Net 6 API 專案我們會把組態設定放到 &lt;code&gt;appsettings.json&lt;/code&gt; 中，而在開發 Azure Functions 的時候，則會需要用到 &lt;code&gt;host.json&lt;/code&gt; 和 &lt;code&gt;local.settings.json&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/oeWBtKj.webp&#34;
  alt=&#34;Image&#34;width=&#34;621&#34; height=&#34;288&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;host.json&lt;/code&gt; 用來設定站台相關的組態，例如在先前&lt;a href=&#34;https://igouist.github.io/post/2022/08/azure-function-servicebus-trigger-max-auto-renew-duration/&#34;&gt;調整 ServiceBus Trigger 的時候&lt;/a&gt;，我們就是在 &lt;code&gt;host.json&lt;/code&gt; 修改重新傳遞訊息到 Azure Functions 的時間。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;local.settings.json&lt;/code&gt; 則是讓我們在本機開發時使用，對應到線上的組態設定：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/LOjCKc3.webp&#34;
  alt=&#34;Image&#34;width=&#34;1829&#34; height=&#34;888&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可以看到 Azure 上的組態有「應用程式設定」和「連接字串」兩個區塊，而在本機開發時會對應到「Values」以及「ConnectionStrings」&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/c4y1dQy.webp&#34;
  alt=&#34;Image&#34;width=&#34;964&#34; height=&#34;279&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有個大概的認識之後，現在讓我們填入一些值吧。&lt;/p&gt;
&lt;p&gt;現在我希望能從組態中，使用 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0&#34;&gt;IOptions&lt;/a&gt; 取得查詢公車估計時間相關的設定，因此我先建立了一個 Class &lt;code&gt;BusTrackerOption&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 公車到站監聽設定&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BusTrackerOption&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 公車路線名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; RouteName { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 要預估到站的站牌 ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; StopId { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 公車路線行進方向&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Direction { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;並且到 &lt;code&gt;local.settings.json&lt;/code&gt; 加上組態。這邊要特別提到的是：原本我們在 &lt;code&gt;appsettings.json&lt;/code&gt; 加上組態，會用這種階層式的寫法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;BusTracker&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;RouteName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;307&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;StopId&amp;#34;&lt;/span&gt; : &lt;span style=&#34;color:#ae81ff&#34;&gt;15250&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Direction&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但我們前面已經看到了，&lt;strong&gt;在 Azure 上的組態實際上是一個單層的列表。因此我們會使用 &lt;code&gt;__&lt;/code&gt; 當作分隔符號來將設定攤平，並放到 &lt;code&gt;local.settings.json&lt;/code&gt; 的 &lt;code&gt;Values&lt;/code&gt; 裡&lt;/strong&gt;（如果是連線字串就放到 &lt;code&gt;ConnectionStrings&lt;/code&gt; 裡），也就是像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Values&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...這裡會有其他組態設定
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 加上 BusTracker 的 參數，用 __ 來表示階層
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;BusTracker__RouteName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;307&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;BusTracker__StopId&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;15250&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;BusTracker__Direction&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們到 &lt;code&gt;Program.cs&lt;/code&gt; 來將 json 的設定值繫結到 &lt;code&gt;BusTrackerOption&lt;/code&gt;，Section 名稱要記得和 json 中的階層名稱對上&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 注意 Azure Functions 的 settings.json 的階層要用 __ 來區隔&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddOptions();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.Configure&amp;lt;BusTrackerOption&amp;gt;(hostContext.Configuration.GetSection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BusTracker&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;註：如果像我一樣開 .Net 6，只看到 &lt;code&gt;HostBuilder&lt;/code&gt; 的朋友，可以自行加入 &lt;code&gt;ConfigureServices((hostContext, services) =&amp;gt; {})&lt;/code&gt; 的部份來註冊，如下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/I6DFiRl.webp&#34;
  alt=&#34;Image&#34;width=&#34;962&#34; height=&#34;339&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著就可以回到我們的 &lt;code&gt;BusReminderFunctions&lt;/code&gt; 來把 &lt;code&gt;IOptions&amp;lt;BusTrackerOption&amp;gt;&lt;/code&gt; 注入進來：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BusReminderFunctions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ILogger _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; BusTrackerOption _busTrackerOption;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; BusReminderFunctions(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ILoggerFactory loggerFactory,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IOptions&amp;lt;BusTrackerOption&amp;gt; busTrackerOption)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger = loggerFactory.CreateLogger&amp;lt;BusReminderFunctions&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _busTrackerOption = busTrackerOption.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...略&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;對依賴注入還不太熟悉的朋友，可以在這邊的建構式裡宣告出 &lt;code&gt;BusTrackerOption&lt;/code&gt; 並賦值即可。但還是推薦閱讀&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/&#34;&gt;依賴注入&lt;/a&gt;的筆記來了解一下呦&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在公車路線等組態都準備得差不多了，只剩下回傳時候要用來接收資料的 Model 還沒開。這時候就直接偷懶，拿 &lt;a href=&#34;https://ptx.transportdata.tw/MOTC?t=Bus&amp;amp;v=2#&#34;&gt;MOTC Transport API&lt;/a&gt; 的 Swagger 打回來的 Json，直接到 &lt;a href=&#34;https://json2csharp.com/&#34;&gt;Json2Cshrp&lt;/a&gt; 之類的轉換網站直接產生 C# Class 就好了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/NJ6HTbA.webp&#34;
  alt=&#34;Image&#34;width=&#34;1002&#34; height=&#34;711&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/RXzItDb.webp&#34;
  alt=&#34;Image&#34;width=&#34;705&#34; height=&#34;530&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;記得轉換結果的 &lt;code&gt;Class Root&lt;/code&gt; 要改個名稱，這邊就直接取名叫做 &lt;code&gt;BusEstimateInfo&lt;/code&gt; 吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9SoUyu9.webp&#34;
  alt=&#34;Image&#34;width=&#34;540&#34; height=&#34;422&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我們有傳過去的參數，也有接回來的 Model 了，讓我們來開一個私有方法，從 MOTC API 取回資料吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢公車估計到站時間&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;routeName&amp;#34;&amp;gt;路線名稱&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;stopId&amp;#34;&amp;gt;站牌 ID&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;direction&amp;#34;&amp;gt;去返程&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;BusEstimateInfo&amp;gt; FetchBusEstimateTime(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; routeName,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; stopId,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; direction = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; api = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;https://ptx.transportdata.tw/MOTC/v2/Bus/EstimatedTimeOfArrival/City/Taipei/{routeName}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 將站牌 ID 及路線方向 加入到查詢參數中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; filter = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;stopId eq &amp;#39;{stopId}&amp;#39;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (direction != &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        filter += &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; and direction eq {direction}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; query = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [&amp;#34;$filter&amp;#34;]&lt;/span&gt; = filter,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [&amp;#34;$top&amp;#34;]&lt;/span&gt; = &lt;span style=&#34;color:#ae81ff&#34;&gt;1.&lt;/span&gt;ToString(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [&amp;#34;$format&amp;#34;]&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JSON&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 使用套件 Microsoft.AspNetCore.WebUtilities 提供的 QueryHelpers &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 直接組出帶 QueryString 的 Url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; url = QueryHelpers.AddQueryString(api, query);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 建立 request；注意直接呼叫 API 會要求驗證&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 必須將 Header 掛成瀏覽器才能吃到每日 50 次的呼叫額度&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; request = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HttpRequestMessage
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Method = HttpMethod.Get,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        RequestUri = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    request.Headers.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;User-Agent&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64)&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HttpClient();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; response = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; client.SendAsync(request);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (response.IsSuccessStatusCode &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Exception(response.StatusCode.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; body = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; response.Content.ReadAsStringAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = JsonSerializer.Deserialize&amp;lt;IEnumerable&amp;lt;BusEstimateInfo&amp;gt;&amp;gt;(body)?.FirstOrDefault();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result ?? &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Exception(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;呼叫 API 失敗，無法取得估計到站資訊&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這支呼叫 API 的 Method 步驟相當簡單：組出參數，呼叫 API，取回資料。&lt;/p&gt;
&lt;p&gt;這邊有偷懶不想組字串，直接使用 &lt;code&gt;Microsoft.AspNetCore.WebUtilities&lt;/code&gt; 的 &lt;code&gt;QueryHelpers.AddQueryString&lt;/code&gt; 來把 GET 的 QueryString 參數組到 Url 裡，不想多安裝一個套件的朋友也可以自己手動組。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：&lt;a href=&#34;https://motc-ptx-api-documentation.gitbook.io/motc-ptx-api-documentation/hui-yuan-shen-qing/membertype&#34;&gt;MOTC 的 文件&lt;/a&gt; 有提到非會員一天有 50 次的免費 Swagger 呼叫次數，實測如果是從程式呼叫時會需要吃驗證，要掛 Header 假裝成瀏覽器才能取得資料，不確定是不是還有其他限制。如果是要給自己開發的工具或產品使用的話，還是可以考慮了解一下 MOTC 的會員制度。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;註：這邊的 HttpClient 也可以直接改用注入 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests&#34;&gt;HttpClientFactory&lt;/a&gt; 的方式來製作。可以參照 Yowko&amp;rsquo;s Notes 的這篇：&lt;a href=&#34;https://blog.yowko.com/httpclient/&#34;&gt;在 .NET Core 與 .NET Framework 上使用 HttpClientFactory&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在取得公車資料的 &lt;code&gt;FetchBusEstimateTime&lt;/code&gt; 已經建好了，讓我們調整一下外面的 Trigger 和共通的私有方法，來把 &lt;code&gt;BusTrackerOption&lt;/code&gt; 的資訊帶到參數裡吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; BusReminderFunctions(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IOptions&amp;lt;BusTrackerOption&amp;gt; busTrackerOption,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ILoggerFactory loggerFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _busTrackerOption = busTrackerOption.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _logger = loggerFactory.CreateLogger&amp;lt;BusReminderFunctions&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Function(&amp;#34;BusReminder-TimerTrigger&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task RunTimerTrigger([TimerTrigger(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0 45 9 * * *&amp;#34;&lt;/span&gt;)] MyInfo myTimer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; routeName = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._busTrackerOption.RouteName;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; stopId = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._busTrackerOption.StopId;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; direction = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._busTrackerOption.Direction;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; TrackBusAsync(routeName, stopId, direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Function(&amp;#34;BusReminder&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;HttpResponseData&amp;gt; HttpTrigger(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpTrigger(AuthorizationLevel.Anonymous, &amp;#34;get&amp;#34;, &amp;#34;post&amp;#34;)]&lt;/span&gt; HttpRequestData req,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    FunctionContext executionContext)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; routeName = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._busTrackerOption.RouteName;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; stopId = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._busTrackerOption.StopId;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; direction = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._busTrackerOption.Direction;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; TrackBusAsync(routeName, stopId, direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; response = req.CreateResponse(HttpStatusCode.OK);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; response.WriteAsJsonAsync&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;object&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; { result = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; response;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task TrackBusAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; routeName,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; stopId,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; direction = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; busEstimateInfo = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; FetchBusEstimateTime(routeName, stopId, direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就像前面提到的，我們在這邊讓 &lt;code&gt;TimerTrigger&lt;/code&gt; 和 &lt;code&gt;HttpTrigger&lt;/code&gt; 取出 &lt;code&gt;BusTrackerOption&lt;/code&gt; 的公車相關參數，例如公車路線名稱後，再傳遞到共用的 &lt;code&gt;TrackBusAsync&lt;/code&gt;，現在第一部份的查詢公車資訊已經完工，就讓我們先添加上去。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：這邊之所以要讓查詢參數交由外面決定，就是以後如果我想把 &lt;code&gt;HttpTrigger&lt;/code&gt; 改成可以從外部呼叫 API 時可以指定公車路線之類的參數，又或者是將來要註冊多組公車路線到資料庫之類的改動時，可以不用再動「查詢公車到站資訊」之類的邏輯。&lt;del&gt;畢竟懶惰的訣竅在於提早準備&lt;/del&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;發送-line-notify&#34;&gt;發送 Line Notify&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.10 更新: Line Notify 將於 2025 年 3 月停止服務（&lt;a href=&#34;https://notify-bot.line.me/closing-announce&#34;&gt;LINE Notify 結束服務公告&lt;/a&gt;），有看到這篇的朋朋請選擇一組新的通知服務來串吧 QQ&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在搞定取得公車資訊的段落了，接著就讓我們來撰寫發送 Line Notify 的部份吧。&lt;/p&gt;
&lt;p&gt;這邊為了過程完整一點，就重播一下上次衝刺去申請 Line Notify Token 的過程吧：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;當下一個直衝 &lt;a href=&#34;https://notify-bot.line.me/zh_TW/&#34;&gt;Line Notify&lt;/a&gt; 高速申請權杖：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/StPJo9W.webp&#34;
  alt=&#34;Image&#34;width=&#34;264&#34; height=&#34;210&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/W43nwLq.webp&#34;
  alt=&#34;Image&#34;width=&#34;506&#34; height=&#34;304&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Ahl9FoN.webp&#34;
  alt=&#34;Image&#34;width=&#34;511&#34; height=&#34;357&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;好的回想完畢，現在讓我們把拿到的 Line Notify Token 也丟到 &lt;code&gt;local.settings.json&lt;/code&gt; 的 &lt;code&gt;Values&lt;/code&gt; 裡方便管理：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Values&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...這裡會有其他組態設定
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 加上 LineNotify 的 Token，用 __ 來表示階層
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;LineNotify__Token&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR LINE NOTIFY TOKEN&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一樣建立一個 Class，方便後續用 &lt;code&gt;IOption&lt;/code&gt; 從組態中取得值：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LineNotifyOptions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Token { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著到 &lt;code&gt;Program.cs&lt;/code&gt; 來將 json 的設定值繫結到 &lt;code&gt;LineNotifyOptions&lt;/code&gt;，Section 名稱要記得和 json 中的名稱對上：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.Configure&amp;lt;LineNotifyOption&amp;gt;(hostContext.Configuration.GetSection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LineNotify&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/61Fs36d.webp&#34;
  alt=&#34;Image&#34;width=&#34;971&#34; height=&#34;365&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就可以回到我們的 &lt;code&gt;BusReminderFunctions&lt;/code&gt; 來把 &lt;code&gt;IOptions&amp;lt;LineNotifyOptions&amp;gt;&lt;/code&gt; 注入進來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aYwNOod.webp&#34;
  alt=&#34;Image&#34;width=&#34;679&#34; height=&#34;425&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣從 Config 取得 Token 的準備就完成了，現在讓我們來撰寫傳送訊息的方法本體吧&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;發送 Line Notify 實際上就是傳送一個 Post 請求到 &lt;code&gt;/api/notify&lt;/code&gt;，並將 Token 放到 Header 的 &lt;code&gt;Authorization&lt;/code&gt;、訊息丟到 Body 就行了&lt;/strong&gt;，因此我們可以直接把這方法包裝成接收到訊息就傳遞給 API：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 發送 Line Notify 通知&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;message&amp;#34;&amp;gt;The message.&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;exception cref=&amp;#34;System.Exception&amp;#34;&amp;gt;發送失敗&amp;lt;/exception&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task SendLineNotify(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.IsNullOrEmpty(_lineNotifyOptions.Token))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Exception(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;取得 Line Notify Token 失敗&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; lineNotifyApi = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://notify-api.line.me/api/notify&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; requestBody = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [&amp;#34;message&amp;#34;]&lt;/span&gt; = message
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; request = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HttpRequestMessage
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Method = HttpMethod.Post,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        RequestUri = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(lineNotifyApi),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Content = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FormUrlEncodedContent(requestBody)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    request.Headers.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bearer {_lineNotifyOptions.Token}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; HttpClient();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; response = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; client.SendAsync(request);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (response.IsSuccessStatusCode &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Exception(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Line Notify 發送失敗&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在我們也已經有了傳送訊息的方法，是時候把它們倆串起來了！&lt;/p&gt;
&lt;h3 id=&#34;組合查詢公車資訊與發送到站通知&#34;&gt;組合查詢公車資訊與發送到站通知&lt;/h3&gt;
&lt;p&gt;先把鏡頭回到我們讓 Trigger 們共用的 &lt;code&gt;TrackBusAsync&lt;/code&gt; 方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task TrackBusAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; routeName,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; stopId,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; direction = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; busEstimateInfo = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; FetchBusEstimateTime(routeName, stopId, direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到我們已經嘗試取得公車資訊，現在我們要組裝訊息，然後傳遞到剛剛撰寫的 Line Notify 通知方法裡。&lt;/p&gt;
&lt;p&gt;而我想要的訊息是這樣的：「您追蹤的公車 307 將在 10 分鐘後（18:00）抵達 台北車站（忠孝）」&lt;/p&gt;
&lt;p&gt;因此我會需要計算出剩餘的分數，以及抵達時間，再從前面拿到的公車資訊來組成訊息並傳遞到方法中：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task TrackBusAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; routeName,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; stopId,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; direction = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; busEstimateInfo = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; FetchBusEstimateTime(routeName, stopId, direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 將預估幾秒後抵達 轉換成 預估幾分鐘後抵達&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; estimateMin = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TimeSpan(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, busEstimateInfo.EstimateTime).Minutes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 取得台北時區的目前時間，並和預估秒數計算出預估抵達時間&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; timeZone = TimeZoneInfo.FindSystemTimeZoneById(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Taipei Standard Time&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; convertedTime = TimeZoneInfo.ConvertTime(DateTime.UtcNow, timeZone);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; estimateTime = convertedTime.AddSeconds(busEstimateInfo.EstimateTime).ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;HH:mm&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;您追蹤的公車 {busEstimateInfo.RouteName.Zh_tw} 將在 {estimateMin} 分鐘後（{estimateTime}）抵達 {busEstimateInfo.StopName.Zh_tw}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; SendLineNotify(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊要特別注意取得時間的部份，因為在伺服器的時間會是 UTC+0，因此我們這邊使用 &lt;code&gt;TimeZoneInfo.ConvertTime&lt;/code&gt; 轉換成台北時區的時間。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：像這種丟到雲端平台的服務，會時常遇到時區時間的轉換。單純的時間轉換可以參照之前筆記的 &lt;a href=&#34;https://igouist.github.io/post/2020/08/csharp-timezone/&#34;&gt;C#: 時區轉換、民國西元、國曆農曆、中文月份週期&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;或是可以嘗試更優雅的 DateTimeOffset，請參考 &lt;a href=&#34;https://demo.tc/post/%E9%82%84%E5%9C%A8%E7%94%A8%20DateTime%20%E5%97%8E%EF%BC%9F%E8%A9%A6%E8%A9%A6%20DateTimeOffset%20%E5%90%A7&#34;&gt;還在用 DateTime 嗎？試試 DateTimeOffset 吧 - demo小鋪&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在我們已經將前面撰寫的兩個小方法組裝起來，是時候來測試看看了！&lt;/p&gt;
&lt;h3 id=&#34;本機測試-functions&#34;&gt;本機測試 Functions&lt;/h3&gt;
&lt;p&gt;總之，啟動鍵先用力給它按下去：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1U9G9IP.webp&#34;
  alt=&#34;Image&#34;width=&#34;154&#34; height=&#34;34&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就會看到小黑窗跑起來，告訴你已經啟動了哪些 Functions：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DCiEz7z.webp&#34;
  alt=&#34;Image&#34;width=&#34;840&#34; height=&#34;540&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到我們的 Http Trigger 以及 Timer Trigger&lt;/p&gt;
&lt;p&gt;因為我們的 Http Trigger 有支援 GET，所以這邊直接複製 Api 網址丟到瀏覽器打看看就可以試囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/oJjtbAg.webp&#34;
  alt=&#34;Image&#34;width=&#34;399&#34; height=&#34;135&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mcji7B5.webp&#34;
  alt=&#34;Image&#34;width=&#34;709&#34; height=&#34;116&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這時候小黑窗也會記錄到這次呼叫：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yENwGQm.webp&#34;
  alt=&#34;Image&#34;width=&#34;841&#34; height=&#34;540&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看起來運行良好，該放上 Azure 上啦！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：在 Azure 上要看小黑窗的話，可以從左邊選單找到「監視 &amp;gt; 紀錄資料流」：



&lt;img
  src=&#34;https://image.igouist.net/PPonKeW.webp&#34;
  alt=&#34;Image&#34;width=&#34;901&#34; height=&#34;604&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;使用-visual-studio-發佈到-azure-functions&#34;&gt;使用 Visual Studio 發佈到 Azure Functions&lt;/h2&gt;
&lt;p&gt;現在讓我們把寫好的 Functions 佈到前面申請的 Azure Functions 服務上運行。為了方便這邊就使用 Visual Studio 來示範，首先讓我們對專案點選發佈：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/I99j6Ul.webp&#34;
  alt=&#34;Image&#34;width=&#34;621&#34; height=&#34;334&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著選取發佈目標，我們想要發到 Azure 雲端上：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1Kh4BNi.webp&#34;
  alt=&#34;Image&#34;width=&#34;804&#34; height=&#34;564&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著會讓我們選擇目標，因為我們前面建立時的作業系統是選用建議的 Windows，因此選擇 Azure Functions (Windows)：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2uslDph.webp&#34;
  alt=&#34;Image&#34;width=&#34;804&#34; height=&#34;564&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就可以選取符合條件的 Azure Functions，這邊當然是選擇我們前面建立好的「林北ㄟ Functions」&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7bMyFxY.webp&#34;
  alt=&#34;Image&#34;width=&#34;804&#34; height=&#34;564&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：如果先前沒有建立過 Azure Functions 服務，發佈的時候也可以從 Visual Studio 中建立，可以說是相當貼心：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PWQvV8K.webp&#34;
  alt=&#34;Image&#34;width=&#34;229&#34; height=&#34;61&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;按下完成之後，我們的發佈檔就建立完囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/eOyR58Z.webp&#34;
  alt=&#34;Image&#34;width=&#34;1013&#34; height=&#34;715&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊要特別注意的是，我們先前有在 &lt;code&gt;local.settings.json&lt;/code&gt; 加入一些設定值，例如 BusTracker 的公車路線等設定。因此我們這邊可以順手同步到目標服務上，在裝載的右上角點開「管理 Azure App Service 設定」：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ojWSenU.webp&#34;
  alt=&#34;Image&#34;width=&#34;1066&#34; height=&#34;376&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;就可以看到我們目前在 Local 的組態設定值，以及 Azure 服務裡的組態設定值。這邊為了將 Local 的設定值同步上去，就直接按下「插入本機的值」：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zxCxX9z.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;450&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：按下「插入本機的值」並確定後，設定值就會同步到 Azure Functions 服務的組態中：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/750ONx8.webp&#34;
  alt=&#34;Image&#34;width=&#34;975&#34; height=&#34;808&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;反過來說，如果沒有從本機同步上去，而是在 Azure 上設定組態也是完全 OK 的&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在一切就緒，讓我們按下發佈吧！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KMZYA70.webp&#34;
  alt=&#34;Image&#34;width=&#34;1031&#34; height=&#34;534&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就可以看到我們撰寫的 Functions 被發佈上去，並且幫我們重啟了 Azure Functions 服務：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OImQVyw.webp&#34;
  alt=&#34;Image&#34;width=&#34;606&#34; height=&#34;151&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在讓我們回到 Azure Functions 服務的函式看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ux7MrMs.webp&#34;
  alt=&#34;Image&#34;width=&#34;940&#34; height=&#34;435&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以確認我們撰寫的 Functions 已經出現囉！&lt;/p&gt;
&lt;p&gt;回到概觀來複製一下我們 Azure Functions 的 Url：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/FsFomcm.webp&#34;
  alt=&#34;Image&#34;width=&#34;1554&#34; height=&#34;380&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且加上我們的 HttpTrigger 路由 &lt;code&gt;api/BusReminder&lt;/code&gt; 呼叫看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HQQVi1t.webp&#34;
  alt=&#34;Image&#34;width=&#34;609&#34; height=&#34;137&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3O0rAbZ.webp&#34;
  alt=&#34;Image&#34;width=&#34;709&#34; height=&#34;116&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;大功告成！&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;終於把這個小提醒給上雲啦！唉呀不得不說 Azure Functions 真是香。&lt;/p&gt;
&lt;p&gt;接下來只要下班前收到公車到站通知，然後在公車來之前下班就可以啦！&lt;/p&gt;
&lt;p&gt;……只要在公車來之前下班，就可以……吧？&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;公司前輩的 Azure Functions 範例&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10202960&#34;&gt;Azure Functions ⚡ 介紹及Serverless入門輕鬆學 - iT 邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/armycoding/2019/03/02/155945&#34;&gt;使用 Azure Function 輕易打造屬於自己的Web API | 工程良田的小球場 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mybaseball52.medium.com/build-a-function-app-732eecec39a1&#34;&gt;建立一個 function App. 使用 Azure Functions | by (KJH) Kuan-Jung, Huang | Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://markheath.net/post/azure-functions-isolated&#34;&gt;Is it time to start creating C# Azure Functions in isolated mode? (markheath.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://techcommunity.microsoft.com/t5/apps-on-azure-blog/net-on-azure-functions-roadmap/ba-p/2197916&#34;&gt;.NET on Azure Functions Roadmap - Microsoft Tech Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>使用 Powershell &#43; 工作排程器 &#43; Line Notify 來定時提醒公車到站時間</title>
      <link>https://igouist.github.io/post/2022/09/bus-reminder-1-powershell-and-windows-task-scheduler/</link>
      <pubDate>Sat, 03 Sep 2022 14:40:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/09/bus-reminder-1-powershell-and-windows-task-scheduler/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BLYVPi7.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;600&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;事情發生在一個風和日麗的平凡下午：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我：（把手上的事情弄到一個段落再下班吧）&lt;br/&gt;
&lt;br/&gt;
～～十分鐘過後～～&lt;br/&gt;
&lt;br/&gt;
我：差不多可以走了，公車也差不多要來了叭？&lt;br/&gt;
公車：（一分鐘前離站）&lt;br/&gt;
我：(ﾟдﾟ)&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這時候才明白愛恨情仇，最傷最痛是後悔。如果我早知道公車快到站了，也許我就不會錯過。&lt;/p&gt;
&lt;p&gt;抱著這股傷痛，決定乾脆寫個小腳本，每天下班提醒我一下，避免重蹈覆轍。&lt;/p&gt;
&lt;p&gt;綜上所述！目標是：&lt;strong&gt;每天下班前十分鐘，告訴我下一班到達的公車時間&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此至少能夠拆分成三個階段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每天下班前十分鐘（定時執行）&lt;/li&gt;
&lt;li&gt;告訴我（通知功能）&lt;/li&gt;
&lt;li&gt;下一班到達的公車時間（查詢資訊）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那麼，我們開始吧！&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-motc-api-服務取得公車資訊&#34;&gt;使用 MOTC API 服務取得公車資訊&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-powershell-呼叫-api-取得資料&#34;&gt;使用 Powershell 呼叫 API 取得資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-powershell-彈出通知視窗初版&#34;&gt;使用 Powershell 彈出通知視窗（初版）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-powershell-傳送-line-notify&#34;&gt;使用 Powershell 傳送 Line Notify&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-工作排程器-定時執行-powershell-腳本&#34;&gt;使用 工作排程器 定時執行 Powershell 腳本&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#後日談&#34;&gt;後日談&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;使用-motc-api-服務取得公車資訊&#34;&gt;使用 MOTC API 服務取得公車資訊&lt;/h2&gt;
&lt;p&gt;首先，最重要的是要有資料來源。幸好我們先前在 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;Api 筆記&lt;/a&gt;的時候，就有介紹過公共運輸動態服務 MOTC Transport API，我們只需要使用這組 API 就能輕鬆拿到公車資訊了，感謝開發該服務的朋朋。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.11.25 更新：發現 MOTC 的 API 已經下架了，現在公車資訊已經被整合到運輸資料服務 &lt;a href=&#34;https://tdx.transportdata.tw/api-service/swagger/basic/2998e851-81d0-40f5-b26d-77e2f5ac4118#/&#34;&gt;TDX (Transport Data eXchange)&lt;/a&gt;，有要動手串公車資訊的朋友可能得觀察新版 API 再進行調整了 QQ&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;我們的場景是在辦公室查詢下一班目標公車到站時間，比對了 Swagger 上提供的中文敘述後，決定嘗試看看「市區公車之預估到站資料」（&lt;code&gt;EstimatedTimeOfArrival/City/{City}/{RouteName}&lt;/code&gt;） 這支 API&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：為了避免被查水表，以下就用台北的 307 號公車為例，並假設目標站點是「台北車站（忠孝）」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;嘗試把縣市和公車路線名稱代入後，可以取得公車行經的站牌資訊，其中就有我們最想要的估計到站時間（秒）：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GQ9wdqG.webp&#34;
  alt=&#34;Image&#34;width=&#34;1000&#34; height=&#34;586&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們調整一下參數，把取前幾筆的 &lt;code&gt;$top&lt;/code&gt; 調大一點，來取得我們目標站點台北車站的站牌 ID：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mixE42V.webp&#34;
  alt=&#34;Image&#34;width=&#34;994&#34; height=&#34;576&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有了站牌 ID，我們就可以根據 &lt;a href=&#34;https://motc-ptx-api-documentation.gitbook.io/motc-ptx-api-documentation/api-te-se/odata&#34;&gt;官方提供的查詢語法&lt;/a&gt; 來篩選出目標站牌：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Njwg0fh.webp&#34;
  alt=&#34;Image&#34;width=&#34;603&#34; height=&#34;769&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果有需要指定回程，也可以再加上 &lt;code&gt;and direction eq 1&lt;/code&gt; 的條件。&lt;/p&gt;
&lt;p&gt;如此一來我們就可以呼叫 MOTC API 來取得目標站牌＋指定公車的到站估計時間囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fkwZZpB.webp&#34;
  alt=&#34;Image&#34;width=&#34;993&#34; height=&#34;517&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：沒有申請&lt;a href=&#34;https://motc-ptx-api-documentation.gitbook.io/motc-ptx-api-documentation/hui-yuan-shen-qing/membertype&#34;&gt;會員&lt;/a&gt;的話有每日 50 次的使用上限，不過這次的目標也就下班打那一次，十分足夠了 XD&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;使用-powershell-呼叫-api-取得資料&#34;&gt;使用 Powershell 呼叫 API 取得資料&lt;/h2&gt;
&lt;p&gt;現在已經保障了資料來源，接下來就是要有個腳本來去打 API 拿資料回來囉！&lt;/p&gt;
&lt;p&gt;基於 &lt;del&gt;懶惰&lt;/del&gt; 方便的原則，決定用 Powershell 寫個小東西直接打資料回來就好。&lt;/p&gt;
&lt;p&gt;這邊就直接用路線名稱和站牌 ID 來串 Uri，並且直接用 &lt;strong&gt;Invoke-RestMethod&lt;/strong&gt; 來呼叫 API 吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$busName &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;307&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 公車路線名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$stopId &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;15250&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# 站台 ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 呼叫 MOTC API 取得公車資訊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$uri &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://ptx.transportdata.tw/MOTC/v2/Bus/EstimatedTimeOfArrival/City/Taipei/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;$busName&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;?%24filter=StopId%20eq%20&amp;#39;&lt;/span&gt;$stopId&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;amp;%24top=1&amp;amp;%24format=JSON&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Invoke-RestMethod -Uri $uri
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 把結果轉成 Json 確認一下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$response | ConvertTo-Json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;小提醒：原本我們下的 &lt;code&gt;$filter = StopId eq &#39;15250&#39;&lt;/code&gt; 的參數，其中 &lt;code&gt;$&lt;/code&gt; 和空白符在 Uri 會轉換成 HTML 編碼的 &lt;code&gt;%24($)&lt;/code&gt; 和 &lt;code&gt;%20(空白)&lt;/code&gt;，並不是亂碼，請不要緊張&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;另存成 &lt;code&gt;.ps1&lt;/code&gt; 檔案來測試一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nVZb7aV.webp&#34;
  alt=&#34;Image&#34;width=&#34;1060&#34; height=&#34;480&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看來查詢資料已經沒有問題了，接下來就是通知快要下班的我了&lt;/p&gt;
&lt;h2 id=&#34;使用-powershell-彈出通知視窗初版&#34;&gt;使用 Powershell 彈出通知視窗（初版）&lt;/h2&gt;
&lt;p&gt;第一次嘗試採用了彈跳視窗，想了一想還是順手記錄下來好了&lt;/p&gt;
&lt;p&gt;首先將我們目標的 &lt;code&gt;EstimateTime&lt;/code&gt; 取出來，然後顯示還有幾分鐘到站、預計幾點幾分到站&lt;/p&gt;
&lt;p&gt;最後用 &lt;code&gt;Wscript.Shell&lt;/code&gt; 來顯示彈跳視窗：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$estimateSec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $response.EstimateTime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 組裝要顯示的訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$estimateMin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $estimateSec / &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$estimateTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Get-date&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;.AddSeconds&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$estimateSec&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;.ToString&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;HH:mm&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bus &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;$busName&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; - EstimateTime: in &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;Math&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;::Floor&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$estimateMin&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;) minute(s), &lt;/span&gt;$estimateTime&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 使用彈跳視窗將預計抵達的時間列印出來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$wshell &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; New-Object -ComObject Wscript.Shell
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$wshell.Popup&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$message&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;雖然運作順利，但是……&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/psyAP4Y.webp&#34;
  alt=&#34;Image&#34;width=&#34;284&#34; height=&#34;143&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;總覺得有點不是很好看啊，而且工作到一半在畫面正中間跳出這個會煩死吧囧&lt;/p&gt;
&lt;h2 id=&#34;使用-powershell-傳送-line-notify&#34;&gt;使用 Powershell 傳送 Line Notify&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.10 更新: Line Notify 將於 2025 年 3 月停止服務（&lt;a href=&#34;https://notify-bot.line.me/closing-announce&#34;&gt;LINE Notify 結束服務公告&lt;/a&gt;），有看到這篇的朋朋請選擇一組新的通知服務來串吧 QQ&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;就在此時一個靈光乍現，對呀我&lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;之前爬訂便當的時候&lt;/a&gt;不是用過 &lt;strong&gt;Line Notify&lt;/strong&gt; 嗎！&lt;/p&gt;
&lt;p&gt;當下一個直衝 &lt;a href=&#34;https://notify-bot.line.me/zh_TW/&#34;&gt;Line Notify&lt;/a&gt; 高速申請權杖：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/StPJo9W.webp&#34;
  alt=&#34;Image&#34;width=&#34;264&#34; height=&#34;210&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/W43nwLq.webp&#34;
  alt=&#34;Image&#34;width=&#34;506&#34; height=&#34;304&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Ahl9FoN.webp&#34;
  alt=&#34;Image&#34;width=&#34;511&#34; height=&#34;357&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;複製權杖之後，衝回 Powershell，&lt;a href=&#34;http://chienleebug.blogspot.com/2017/10/powersehllline-notify.html&#34;&gt;前人教學抄起來&lt;/a&gt;，Invoke-RestMethod 就直接打下去：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 使用 Line Notify 傳送通知&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$lineUri &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://notify-api.line.me/api/notify&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$lineToken &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Bearer YOUR_LINE_TOKEN&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$header &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; Authorization &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $lineToken &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$body &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $message &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod -Uri $lineUri -Method Post -Headers $header -Body $body
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Line 也不負期望地彈出來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BVGpNzg.webp&#34;
  alt=&#34;Image&#34;width=&#34;580&#34; height=&#34;319&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;大功告成！搞定拿資料和通知的部分啦～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果訊息內容有使用&lt;strong&gt;中文&lt;/strong&gt;的朋友，請注意編碼問題。必須存成 &lt;strong&gt;Utf-8 with BOM&lt;/strong&gt;，否則會出現亂碼：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5n7a7ht.webp&#34;
  alt=&#34;Image&#34;width=&#34;580&#34; height=&#34;209&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這時候就需要更改編碼為 Utf-8 with BOM；&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/S5S5Pgu.webp&#34;
  alt=&#34;Image&#34;width=&#34;596&#34; height=&#34;150&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;再重新嘗試一次就會正常了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mMuRRTT.webp&#34;
  alt=&#34;Image&#34;width=&#34;580&#34; height=&#34;169&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;原本卡在這步搞不定，正好黑暗執行緒大大發了篇 &lt;a href=&#34;https://blog.darkthread.net/blog/ps1-encoding-issue/&#34;&gt;PowerShell .ps1 檔 UTF-8 編碼問題之變形錯誤&lt;/a&gt;，才知道 PowerShell 5.x 有編碼解析的問題，改成 Utf-8 with BOM 順利完工。這邊補充給各位朋朋，望周知&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;到這邊 Powershell 的部份就處理好了，目前會長這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$busName &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;307&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 公車路線名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$stopId &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;15250&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# 站台 ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 呼叫 MOTC API 取得公車資訊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$uri &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://ptx.transportdata.tw/MOTC/v2/Bus/EstimatedTimeOfArrival/City/Taipei/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;$busName&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;?%24filter=StopId%20eq%20&amp;#39;&lt;/span&gt;$stopId&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;amp;%24top=1&amp;amp;%24format=JSON&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Invoke-RestMethod -Uri $uri
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$estimateSec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $response.EstimateTime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 組裝要顯示的訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$estimateMin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $estimateSec / &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$estimateTime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Get-date&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;.AddSeconds&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$estimateSec&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;.ToString&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;HH:mm&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bus &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;$busName&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; - EstimateTime: in &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;Math&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;::Floor&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;$estimateMin&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;) minute(s), &lt;/span&gt;$estimateTime&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 使用 Line Notify 傳送通知&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$lineUri &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://notify-api.line.me/api/notify&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$lineToken &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Bearer YOUR_LINE_TOKEN&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$header &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; Authorization &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $lineToken &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$body &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @&lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $message &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod -Uri $lineUri -Method Post -Headers $header -Body $body
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;使用-工作排程器-定時執行-powershell-腳本&#34;&gt;使用 工作排程器 定時執行 Powershell 腳本&lt;/h2&gt;
&lt;p&gt;秉持著前面選擇 Powershell 的 &lt;del&gt;偷懶&lt;/del&gt; 簡單精神，這邊的定時執行就直接使用 Windows 內建的工作排程器來處理：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/R7o2WFS.webp&#34;
  alt=&#34;Image&#34;width=&#34;756&#34; height=&#34;314&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;因為我們的場景相對簡單，只有要在特定時間幫我們呼叫 Powershell 腳本，因此直接選擇「建立基本動作」&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gQU0yt3.webp&#34;
  alt=&#34;Image&#34;width=&#34;352&#34; height=&#34;297&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SUlz1VH.webp&#34;
  alt=&#34;Image&#34;width=&#34;696&#34; height=&#34;518&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們選擇每週，並指定平日的時候再執行：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/v2qCs5B.webp&#34;
  alt=&#34;Image&#34;width=&#34;696&#34; height=&#34;518&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/jlEruE3.webp&#34;
  alt=&#34;Image&#34;width=&#34;696&#34; height=&#34;518&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最後選擇啟動程式，讓工作排程器開啟 Powershell 並呼叫我們的腳本：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SapHUwE.webp&#34;
  alt=&#34;Image&#34;width=&#34;696&#34; height=&#34;518&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊的「程式或指令碼」輸入 &lt;code&gt;powershell&lt;/code&gt;，接著在「新增引數」的部份告訴 Powershell 我們要執行的腳本 &lt;code&gt;-File &amp;quot;C:\Scripts\BusReminder.ps1&amp;quot;&lt;/code&gt;（記得換成你的腳本路徑呦）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7KV6Q6o.webp&#34;
  alt=&#34;Image&#34;width=&#34;696&#34; height=&#34;518&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：沒有調整過&lt;a href=&#34;https://igouist.github.io/post/2020/08/powershell-beauty/#%E5%9F%B7%E8%A1%8C%E5%8E%9F%E5%89%87&#34;&gt;執行原則&lt;/a&gt;的朋友們，可以在引數上加入 &lt;code&gt;-ExecutionPolicy Bypass&lt;/code&gt; 來關閉警告，也就是 &lt;code&gt; -ExecutionPolicy Bypass -File &amp;quot;C:\Scripts\BusReminder.ps1&amp;quot;&lt;/code&gt; 這樣子的感覺&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;註：如果跟我一樣會把腳本做成 Function 並存成 psm1 檔案的朋朋，這邊的引數會需要變成跟 Profile 一樣的處理方式，先 Import 進來再呼叫方法（這邊假設為 &lt;code&gt;Run-BusNotify()&lt;/code&gt;）例如，：&lt;code&gt;Import-Module &amp;quot;C:\Scripts\BusReminder.psm1&amp;quot;;Run-BusNotify;&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著只需要完成就可以在排程中找到囉，馬上就來執行看看是不是正常運作吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1MLM21I.webp&#34;
  alt=&#34;Image&#34;width=&#34;696&#34; height=&#34;518&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Pu8YilJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;510&#34; height=&#34;147&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/R8Ls97x.webp&#34;
  alt=&#34;Image&#34;width=&#34;580&#34; height=&#34;130&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;大功告成！&lt;/p&gt;
&lt;h2 id=&#34;後日談&#34;&gt;後日談&lt;/h2&gt;
&lt;p&gt;自從有了公車到站提醒後，下班再也沒有煩惱了呢&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;提醒：公車還有十分鐘　&lt;br/&gt;
我：（還有十分鐘耶，把手上的事情弄到一個段落再下班吧）&lt;br/&gt;
～～弄了十五分鐘～～&lt;br/&gt;
公車：（離站）&lt;br/&gt;
我：(ﾟдﾟ)&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這時候才明白科技終究是有極限的。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://chienleebug.blogspot.com/2017/10/powersehllline-notify.html&#34;&gt;[PowerSehll] 使用LINE Notify發送訊息 (chienleebug.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ghostyguo.pixnet.net/blog/post/359754775-%E8%A8%AD%E5%AE%9Awindows%E6%8E%92%E7%A8%8B%E5%99%A8%E5%9F%B7%E8%A1%8Cpowershell-script&#34;&gt;設定 Windows 排程器執行 powershell script @ No More Codes (pixnet.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://motc-ptx-api-documentation.gitbook.io/motc-ptx-api-documentation/&#34;&gt;入門指南 - motc-ptx-api-documentation (gitbook.io)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/ps1-encoding-issue/&#34;&gt;PowerShell .ps1 檔 UTF-8 編碼問題之變形錯誤 - 黑暗執行緒 (darkthread.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10032205&#34;&gt;使用 Windows PowerShell 顯示彈出式的對話視窗 - iT 邦幫忙 (ithome.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞抓蟲：Azure Functions ServiceBus Trigger 執行過久時會重複觸發 Functions</title>
      <link>https://igouist.github.io/post/2022/08/azure-function-servicebus-trigger-max-auto-renew-duration/</link>
      <pubDate>Sat, 27 Aug 2022 10:09:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/08/azure-function-servicebus-trigger-max-auto-renew-duration/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/00WQGqR.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;337&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;當發現&lt;strong&gt;需要執行很久的 ServiceBus Trigger Function 有重複執行的情況&lt;/strong&gt;出現時，可以嘗試到&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-bindings-service-bus?tabs=in-process%2Cextensionv5%2Cextensionv3&amp;amp;pivots=programming-language-csharp#hostjson-settings&#34;&gt;官方的 Host.json 設定指引&lt;/a&gt;，按照 SDK 版本找到對應的「&lt;strong&gt;訊息鎖定最大持續時間&lt;/strong&gt;」設定，例如 maxAutoLockRenewalDuration（延伸模組 5.x+）或 maxAutoRenewDuration（Functions 2.x），並加入專案的 Host.json&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因為 ServiceBus 在傳遞訊息之後，如果超過一段時間（MaxAutoRenewDuration）內沒有得到回應，就會解除信件的鎖並嘗試重新傳遞&lt;/strong&gt;，這時候如果原先的 Function 仍在執行，就會一前一後重複執行 Function 並發生許多光怪陸離的事，例如寫入兩筆資訊、重複複製資料之類的。&lt;/p&gt;
&lt;p&gt;建議如果調整了有 ServiceBus Trigger Function 的 Azure Functions Timeout 設定時，或是發現某支 ServiceBus Trigger 的 Functions 執行時間過長，就要一併注意 MaxAutoRenewDuration 的設定，避免重複執行的情況出現。&lt;/p&gt;
&lt;h2 id=&#34;事發原由&#34;&gt;事發原由&lt;/h2&gt;
&lt;p&gt;工作時將一段需要呼叫其他 API、執行相當久的程式片段搬上 &lt;a href=&#34;https://igouist.github.io/post/2022/09/bus-reminder-2-azure-functions-timetrigger-with-line-notify/&#34;&gt;Azure Functions&lt;/a&gt;，並使用 &lt;a href=&#34;https://igouist.github.io/post/2022/08/azure-service-bus&#34;&gt;Service Bus&lt;/a&gt; 來傳遞訊息觸發 Functions 執行，這時卻發現 &lt;strong&gt;Function 被執行了兩次&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;現在就讓我們來重建當時的情況吧。首先我們有個 Service Bus Trigger 的 Azure Function，這邊就直接從 Visual Studio 提供的&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/dotnet-isolated-process-guide&#34;&gt;已隔離（Isolated）&lt;/a&gt;範本進行建立。&lt;/p&gt;
&lt;p&gt;為了重現執行很久的特點，我們讓它 Delay 個八分鐘，並在開始和結束的時候告訴我們一下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// ServiceBus Trigger 測試用 Function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;myQueueItem&amp;#34;&amp;gt;My queue item.&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Function(&amp;#34;ServiceBusTriggerSample&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Run(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [ServiceBusTrigger(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        queueName: &amp;#34;%QueueName%&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        Connection = &amp;#34;ServiceBus&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; myQueueItem)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _logger.LogInformation(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;開始處理訊息: {myQueueItem}&amp;#34;&lt;/span&gt;, myQueueItem);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; Task.Delay(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TimeSpan(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _logger.LogInformation(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;結束處理訊息: {myQueueItem}&amp;#34;&lt;/span&gt;, myQueueItem);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;並且簡單地用之前 &lt;a href=&#34;https://igouist.github.io/post/2022/08/azure-service-bus&#34;&gt;Service Bus 文章&lt;/a&gt; 的範例送個 &amp;ldquo;Hello&amp;rdquo; 進去 Queue 裡，準備觸發 Function：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; context = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusClient(_connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var sender = client.CreateSender(_queueName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusMessage(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; sender.SendMessageAsync(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 Function 接收到訊息後，讓我們觀察 Console：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/dBFWz2p.webp&#34;
  alt=&#34;Image&#34;width=&#34;1033&#34; height=&#34;261&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可以發現第一次執行尚未結束的時候，大概經過五分鐘就又執行了第二次！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;調整訊息鎖定最大持續時間&#34;&gt;調整訊息鎖定最大持續時間&lt;/h2&gt;
&lt;p&gt;原先以為是 Function 執行失敗導致 ServiceBus 重新傳遞之類的狀況，但找了老半天沒有頭緒，嘗試了調整一些設定也沒有起色，陷入了深深的混亂&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UY3EhoA.webp&#34;
  alt=&#34;Image&#34;width=&#34;586&#34; height=&#34;483&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;幸好天無絕人之路，最終在 &lt;a href=&#34;https://stackoverflow.com/questions/62752905/azure-function-service-bus-trigger-running-multiple-times&#34;&gt;Stackoverflow&lt;/a&gt; 海巡的時候了發現一線生機！&lt;/p&gt;
&lt;p&gt;原來 Azure Functions 的 Service Bus Trigger 有個「&lt;strong&gt;訊息鎖定最大持續時間&lt;/strong&gt;」設定！&lt;/p&gt;
&lt;p&gt;當 Service Bus 傳遞訊息到 Function 的時候，Function 會根據執行結果告訴 Service Bus 該訊息要標記成功或是失敗；但如果訊息就這麼一去不回時，Service Bus 會先觀望一下，&lt;strong&gt;直到超過了「訊息鎖定最大持續時間」就會下令解除訊息的鎖定，嘗試重新傳遞&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這次的事件就是因為我們即使調長了 Timeout 時間，但當該 Function 執行超過預設的鎖定時間（五分鐘）時，Service Bus 再度傳遞了訊息，才導致 Function 重複被執行而造成各種奇怪的資料錯誤&lt;/p&gt;
&lt;p&gt;那麼這個「訊息鎖定最大持續時間」怎麼設定呢？我們可以參照&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-bindings-service-bus?tabs=in-process%2Cextensionv5%2Cextensionv3&amp;amp;pivots=programming-language-csharp#hostjson-settings&#34;&gt;官方的 Host.json 設定指引&lt;/a&gt;，按照 SDK 版本找到對應的「訊息鎖定最大持續時間」設定，例如 maxAutoLockRenewalDuration（延伸模組 5.x+）或 maxAutoRenewDuration（Functions 2.x），並加入專案的 Host.json。&lt;/p&gt;
&lt;p&gt;現在讓我們在範例專案加入 maxAutoRenewDuration 的設定，這邊就改成比前面的執行時間八分鐘更長的十分鐘：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TTpXELd.webp&#34;
  alt=&#34;Image&#34;width=&#34;550&#34; height=&#34;298&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著再重新傳遞一次訊息：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2epGiF9.webp&#34;
  alt=&#34;Image&#34;width=&#34;1043&#34; height=&#34;257&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到過程中沒有重新傳遞訊息了，大功告成！&lt;/p&gt;
&lt;p&gt;總之，學到了調整 ServiceBus Trigger Function 的 Azure Functions Timeout 設定時，或是發現某支 ServiceBus Trigger 的 Functions 執行時間過長，就要一併注意 MaxAutoRenewDuration 的設定，避免重複執行。&lt;/p&gt;
&lt;p&gt;如此如此，這般這般，一天又平安的過去了，感謝 Stackoverflow 大大們的努力。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/62752905/azure-function-service-bus-trigger-running-multiple-times&#34;&gt;Azure function service bus trigger running multiple times - Stockoverflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/azure-functions/functions-bindings-service-bus?tabs=in-process%2Cfunctionsv2%2Cextensionv3&amp;amp;pivots=programming-language-csharp#hostjson-settings&#34;&gt;Azure Functions 的 Azure 服務匯流排繫結 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>DevToys —— 開發人員的瑞士刀工具箱</title>
      <link>https://igouist.github.io/post/2022/08/devtoys/</link>
      <pubDate>Sat, 20 Aug 2022 08:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/08/devtoys/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://raw.githubusercontent.com/DevToys-app/DevToys/refs/heads/main/assets/logo/Windows-Linux/Stable/Icon-Windows-Linux.png&#34;
  alt=&#34;&#34;width=&#34;512&#34; height=&#34;512&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;嗨各位朋朋，又双叒叕到了「同事推薦的好用工具」時間！&lt;/p&gt;
&lt;p&gt;今天要介紹的是 &lt;a href=&#34;https://devtoys.app/&#34;&gt;Devtoys&lt;/a&gt;：這是一套包含了許多貼心小工具的工具箱，例如 UUID 產生器、Base64 編碼解碼、JSON Format 都可以在這邊找到，省下 Google 這些小工具的時間。&lt;/p&gt;
&lt;p&gt;進入&lt;a href=&#34;https://devtoys.app/&#34;&gt;官網&lt;/a&gt;或 &lt;a href=&#34;https://www.microsoft.com/store/apps/9pgcv4v3bk4w&#34;&gt;Store&lt;/a&gt; 下載之後，點開就能在畫面上看到一整堆小工具：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/C0fUpI2.webp&#34;
  alt=&#34;Image&#34;width=&#34;1785&#34; height=&#34;816&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;左邊的工具列也有工具分類和搜尋可以使用，具體有哪些工具這邊就不再贅述。可以到 &lt;a href=&#34;https://github.com/veler/DevToys&#34;&gt;Github&lt;/a&gt; 上看一下工具列表，或是就直接下載下來看看更清楚：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6TdYfDu.webp&#34;
  alt=&#34;Image&#34;width=&#34;312&#34; height=&#34;616&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;例如說當我原本複製了一坨 Json，為了保護眼睛和心靈，我就得上網先找個 Json Formatter 來轉一下；現在我知道 Devtoys 就有，直接打開來用就可以。&lt;/p&gt;
&lt;p&gt;如果真的很常用到的工具，也可以加到最愛，就會直接出現在左上角的列表中：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IA0GvHL.webp&#34;
  alt=&#34;Image&#34;width=&#34;320&#34; height=&#34;472&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;同時 DevToys 也支援釘選到 Windows 的「開始」功能表，如果真的很常用也可以直接釘起來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gcE827M.webp&#34;
  alt=&#34;Image&#34;width=&#34;745&#34; height=&#34;501&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tpp49cH.webp&#34;
  alt=&#34;Image&#34;width=&#34;175&#34; height=&#34;318&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其他遇到的場景還有：複製了 JWT Token 想快速解開、想從 Unix 時間戳轉出時間等等&lt;/p&gt;
&lt;p&gt;例如我個人最近常跟排程類的服務打交道，就會需要時常調整 Crontab 語法，而 DevToys 當然也有提供工具來幫助我們迅速確認 Cron 的執行時間。&lt;/p&gt;
&lt;p&gt;像這種時候只要先確認一下 DevToys，也許就能找到對應的工具，下次再遇到就可以省下找工具的時間，可謂是方便方便。&lt;/p&gt;
&lt;p&gt;那麼這次的小工具分享就到這邊，&lt;del&gt;再度成功一個工具水一篇文&lt;/del&gt;，推薦大家下載試試！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ｑ：這類小工具箱還蠻多的吧？為什麼挑這款呢&lt;/p&gt;
&lt;p&gt;Ａ：因為它好看啊，好看就完事了&lt;/p&gt;&lt;/blockquote&gt;</description>
    </item>
    
    <item>
      <title>使用 Azure Service Bus 來建立簡單的訊息佇列（Message Queue）吧</title>
      <link>https://igouist.github.io/post/2022/08/azure-service-bus/</link>
      <pubDate>Sat, 13 Aug 2022 16:50:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/08/azure-service-bus/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5Vube9E.webp&#34;
  alt=&#34;Image&#34;width=&#34;598&#34; height=&#34;565&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在工作上遇到在兩個 Azure 工具間建立訊息佇列（Message Queue）的需求，因此接觸到了 Azure Service Bus（中文：服務匯流排 &lt;del&gt;燴牛排？&lt;/del&gt;），在前輩的協助下建立了一組簡單的 Demo，這就來筆記一下。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#什麼是訊息佇列message-queue-mq&#34;&gt;什麼是訊息佇列（Message Queue, MQ）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#建立-azure-service-bus-資源&#34;&gt;建立 Azure Service Bus 資源&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#建立佇列queue&#34;&gt;建立佇列（Queue）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#取得-servicebus-的連線字串&#34;&gt;取得 ServiceBus 的連線字串&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#將訊息放入佇列&#34;&gt;將訊息放入佇列&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#傳送一則訊息到佇列&#34;&gt;傳送一則訊息到佇列&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#傳送一批訊息到佇列&#34;&gt;傳送一批訊息到佇列&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#傳送物件到佇列&#34;&gt;傳送物件到佇列&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#從佇列取出訊息&#34;&gt;從佇列取出訊息&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#從佇列中讀取一則訊息&#34;&gt;從佇列中讀取一則訊息&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#從佇列中持續讀取訊息&#34;&gt;從佇列中持續讀取訊息&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#補充在-servicebus-explorer-確認訊息&#34;&gt;補充：在 ServiceBus Explorer 確認訊息&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#建立主題topic&#34;&gt;建立主題（Topic）&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#將訊息放入主題&#34;&gt;將訊息放入主題&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#從主題中讀取訊息&#34;&gt;從主題中讀取訊息&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#稍作整理&#34;&gt;稍作整理&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-iazureclientfactory-搭配依賴注入來建立-azure-client&#34;&gt;使用 IAzureClientFactory 搭配依賴注入來建立 Azure Client&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#將連線字串抽取到-config&#34;&gt;將連線字串抽取到 Config&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;什麼是訊息佇列message-queue-mq&#34;&gt;什麼是訊息佇列（Message Queue, MQ）&lt;/h2&gt;
&lt;p&gt;首先讓我們簡單認識一下訊息佇列。假設我們有生產者和消費者兩個服務，其中&lt;strong&gt;生產者負責產生資料，而消費者負責消費這些資料&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mg4lXJk.webp&#34;
  alt=&#34;Image&#34;width=&#34;1200&#34; height=&#34;485&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;各位也可以這樣理解：生產者就像是壽司師傅，他會不斷的捏壽司出來；而這時候來了一位大胃王顧客，他就是消費者，會不斷地把壽司吃掉&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Sk16WdC.webp&#34;
  alt=&#34;Image&#34;width=&#34;1138&#34; height=&#34;465&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;大概對這兩個角色有點認識就行了。那麼，假設我們有兩組 API 服務：其中一個是負責寫入 Log 的服務，而另一個是產品服務。&lt;/p&gt;
&lt;p&gt;產品服務會將 Log 內容丟給 Log 服務去紀錄 Log，這時候產生了這些日誌資料的產品服務就是生產者，而消費這些日誌資料去寫 Log 的服務就是消費者。&lt;/p&gt;
&lt;p&gt;也就是：&lt;strong&gt;&lt;code&gt;產品服務（生產者） —— 資料 —&amp;gt; Log 服務（消費者）&lt;/code&gt;&lt;/strong&gt; 這樣的狀況。&lt;/p&gt;
&lt;p&gt;然而像這樣&lt;strong&gt;直接相依的兩個服務，可能就會遇到一些問題：像是消費者突然掛掉，導致生產者也跟著掛掉；又或是消費者的變動和擴展會連帶影響到生產者必須跟著變動等等。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這樣說可能有些模糊。就以上面提到的 Log 服務來說，在直接呼叫 API 的情況下，我們常常會需要在產品服務中直接明確寫出 Log 服務的資訊，像是站台位址、API 路由之類的。&lt;/p&gt;
&lt;p&gt;但當 Log 服務的站台或方法有變動，我們就被迫要修改產品服務對應的程式碼；而如果我們今天想要擴展 Log 服務的站台，就會需要修改產品服務裡關於 Log 服務的資訊。&lt;/p&gt;
&lt;p&gt;而今天如果 Log 服務短暫掛掉了，可能就會連帶讓我們的產品服務一起掛掉，就算有進行簡單的錯誤處理，當時該記的 Log 內容也就遺失了。&lt;/p&gt;
&lt;p&gt;那面對這些問題的時候怎麼辦呢？這時候我們就可以&lt;strong&gt;在中間加一條佇列&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zrgm8JC.webp&#34;
  alt=&#34;Image&#34;width=&#34;1199&#34; height=&#34;203&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;用前面的壽司店來說就是這樣（很堅持要用壽司舉例）：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/J6aksgo.webp&#34;
  alt=&#34;Image&#34;width=&#34;1200&#34; height=&#34;202&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們加入訊息佇列之後得到的好處有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解除耦合&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;生產者不需要知道消費者的資訊，消費者的變動也不會直接影響到生產者&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高擴展性&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;即使我們要增加消費者的數量，變成三個消費者來處理這些資料，也只要讓佇列處理轉發就好，而不用影響生產者&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非同步&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;生產者現在只要把訊息丟到佇列就可以回頭繼續做自己的事了，不用管也不用等待消費者處理這些訊息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;緩衝&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;佇列讓消費者多了一段緩衝區，即使消費者忙不過來，也有佇列可以讓訊息好好排隊&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除此之外還可以玩一些花式處理，例如藉由佇列來做到限制流量等等，此處先按下不表。&lt;/p&gt;
&lt;p&gt;現在我們大概知道訊息佇列在幹嘛了。接著就讓我們來玩玩 Azure Service Bus 這個訊息佇列服務吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;想更了解 Message Queue 的朋友，也可以閱讀以下的參考資料呦：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/starbugs/%E8%AE%93%E4%BB%BB%E5%8B%99%E6%8E%92%E9%9A%8A%E5%90%A7-message-queue-1-de949e274c43&#34;&gt;[基礎觀念系列] 讓任務排隊吧：Message Queue — (1) - 莫力全 Kyle Mo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&amp;amp;mid=2247485080&amp;amp;idx=1&amp;amp;sn=f223feb9256727bde4387d918519766b&#34;&gt;什么是消息队列？ - Java3y (qq.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://ningg.top/message-queue-intro/&#34;&gt;消息队列（Message Queue）基本概念 | NingG 个人博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10238631&#34;&gt;Message Queue - (1) - iT 邦幫忙 (ithome.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10209296&#34;&gt;Producer Consumer 模式 - iT 邦幫忙 (ithome.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/tw/message-queue/&#34;&gt;什麼是訊息佇列？ (amazon.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;建立-azure-service-bus-資源&#34;&gt;建立 Azure Service Bus 資源&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：Azure Service Bus 是一項收費服務，你可能會想先了解&lt;a href=&#34;https://azure.microsoft.com/zh-tw/pricing/details/service-bus/&#34;&gt;定價&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先讓我們先到 &lt;a href=&#34;https://portal.azure.com/#home&#34;&gt;Azure&lt;/a&gt;，建立一個新的資源：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/s0klgGc.webp&#34;
  alt=&#34;Image&#34;width=&#34;502&#34; height=&#34;302&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;找到 Service Bus 並新建它：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ngSiiJm.webp&#34;
  alt=&#34;Image&#34;width=&#34;614&#34; height=&#34;254&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2TepYi8.webp&#34;
  alt=&#34;Image&#34;width=&#34;492&#34; height=&#34;304&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著選擇要掛資源的帳戶和群組後，替我們的 Service Bus 取個好記的名字。&lt;/p&gt;
&lt;p&gt;這邊示範的方案當然就直接選最便宜的 XD&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6N744Nr.webp&#34;
  alt=&#34;Image&#34;width=&#34;774&#34; height=&#34;554&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了基本設定以外，這邊還能變更最低 TLS 版本（維護老舊專案者注意，或是準備&lt;a href=&#34;https://blog.darkthread.net/blog/net35-tls12-issue/&#34;&gt;踩雷&lt;/a&gt;）等等，可以頁籤都戳一戳。&lt;/p&gt;
&lt;p&gt;確認之後就可以勇敢按下「審核 + 建立」囉！&lt;/p&gt;
&lt;p&gt;按下後會部署個幾分鐘，部屬完畢之後就可以直接前往資源：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/oPmqwpi.webp&#34;
  alt=&#34;Image&#34;width=&#34;700&#34; height=&#34;390&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;抵達我們的 Service Bus 資源頁面：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CvH4MgL.webp&#34;
  alt=&#34;Image&#34;width=&#34;1077&#34; height=&#34;734&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣我們就成功建立 Service Bus 啦！&lt;/p&gt;
&lt;h2 id=&#34;建立佇列queue&#34;&gt;建立佇列（Queue）&lt;/h2&gt;
&lt;p&gt;接著就讓我們來建立一條佇列吧，首先讓我們再看一眼微軟把拔提供的佇列概念圖：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Oi1GOqz.webp&#34;
  alt=&#34;Image&#34;width=&#34;780&#34; height=&#34;137&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;回到 Azure 的 Service Bus 資源頁面，讓我們在畫面上點選建立佇列：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lwNmNQR.webp&#34;
  alt=&#34;Image&#34;width=&#34;577&#34; height=&#34;169&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且取個好名字，等等發訊息的時候會用到：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6ONWYWT.webp&#34;
  alt=&#34;Image&#34;width=&#34;424&#34; height=&#34;525&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這邊會需要注意一下「最大傳遞計數」（Maximum Delivery Count），簡單來說就是這封訊息會嘗試傳遞幾次，如果超過次數都沒有傳遞成功就會被丟到無效信件。可以參閱&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-dead-letter-queues#maximum-delivery-count&#34;&gt;服務匯流排寄不出的信件佇列的概觀&lt;/a&gt;中的「最大傳遞計數」小節&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;這篇我們不會用到下面選項的&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-partitioning&#34;&gt;資料分割&lt;/a&gt;、到期自動轉&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-dead-letter-queues&#34;&gt;無效信件&lt;/a&gt;等功能，有興趣的朋友可以再自己研究一下呦～&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;建立完就會出現在我們 Service Bus 的頁面下方囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Jbzbei8.webp&#34;
  alt=&#34;Image&#34;width=&#34;472&#34; height=&#34;159&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;取得-servicebus-的連線字串&#34;&gt;取得 ServiceBus 的連線字串&lt;/h2&gt;
&lt;p&gt;接著為了讓我們後續可以順利連線，先到左邊的 &lt;strong&gt;「設定 &amp;gt; 共用存取原則」拿到我們的連線字串&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zYkN3cQ.webp&#34;
  alt=&#34;Image&#34;width=&#34;940&#34; height=&#34;701&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊直接使用預設的原則，如果有需要控管不同使用者的權限，例如說某組連線字串只能接收或讀取等等，也可以在這邊新增原則來管理。&lt;/p&gt;
&lt;h2 id=&#34;將訊息放入佇列&#34;&gt;將訊息放入佇列&lt;/h2&gt;
&lt;p&gt;現在 Queue 已經建立起來了，接著就是要嘗試把訊息丟到 Queue 裡面囉！&lt;/p&gt;
&lt;p&gt;為了方便測試，這邊就採用 Visual Studio 內建的 API 樣板，直接在 Controller 簡單建立一個範例，要實際應用在專案中的朋友請再依據專案架構自行調整。&lt;/p&gt;
&lt;p&gt;那麼就讓我們開始吧，首先我們會需要安裝 &lt;strong&gt;Azure.Messaging.ServiceBus&lt;/strong&gt; 這個套件包：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zXuFLdw.webp&#34;
  alt=&#34;Image&#34;width=&#34;942&#34; height=&#34;221&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著是我們本次的範例用 Controller，以及尚未實作傳送訊息的 Function：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// Service Bus Queue 示範用 Controller&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;QueueController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 將訊息放入佇列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Enqueue([FromBody] &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 我們要在這裡實作傳送訊息到 Queue 的方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;傳送一則訊息到佇列&#34;&gt;傳送一則訊息到佇列&lt;/h3&gt;
&lt;p&gt;接著讓我們來傳送訊息吧，這邊會需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 ServiceBus 的連線字串建立 &lt;code&gt;ServiceBusClient&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用 Queue 的名稱，從 &lt;code&gt;ServiceBusClient&lt;/code&gt; 建立 &lt;code&gt;ServiceBusSender&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;ServiceBusSender&lt;/code&gt; 來傳送訊息到 Queue&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 將訊息放入佇列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Enqueue([FromBody] &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 ServiceBus 的連線字串建立 Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 連線字串可以在 Azure ServiceBus 頁面的共用存取原則找到&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ServiceBusClient 用完記得要呼叫 DisposeAsync() 來關掉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 或是直接使用 await using 包起來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusClient(connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 傳遞 Queue 的名字給 CreateSender 方法來建立 Sender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和 ServiceBusClient 一樣，有提供 DisposeAsync 方法來關閉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 或是直接使用 await using 包起來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; queueName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR QUEUE NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var sender = client.CreateSender(queueName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 將要傳送的訊息包裝成 ServiceBusMessage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 並使用 ServiceBusSender.SendMessageAsync 傳送出去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusMessage(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; sender.SendMessageAsync(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;提醒一下將來會跑回來複製貼上的我和各位朋朋：記得把連線字串跟佇列名稱改成你的！&lt;/p&gt;
&lt;p&gt;現在讓我們來呼叫ＡＰＩ，丟個 &lt;code&gt;&amp;quot;Hello&amp;quot;&lt;/code&gt; 進去試試：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ioXGear.webp&#34;
  alt=&#34;Image&#34;width=&#34;1167&#34; height=&#34;883&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著到 Azure Service Bus 的 Queue 介面瞧瞧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1zTEnWt.webp&#34;
  alt=&#34;Image&#34;width=&#34;800&#34; height=&#34;328&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;Queue 也確實收到一則訊息了，看來我們成功把 Hello 丟進去啦！&lt;/p&gt;
&lt;h3 id=&#34;傳送一批訊息到佇列&#34;&gt;傳送一批訊息到佇列&lt;/h3&gt;
&lt;p&gt;當然在實務上，我們有時候會想要傳一卡車的訊息；例如我們剛處理完一批客戶，想把它們丟到佇列去讓另一個服務做點事，這時候如果還得一封一封塞訊息就有點怪怪的。&lt;/p&gt;
&lt;p&gt;雖然我們可以直接粗暴地把 Sender 的 &lt;code&gt;SendMessageAsync&lt;/code&gt; 加個 &lt;code&gt;s&lt;/code&gt; 變成 &lt;code&gt;SendMessagesAsync&lt;/code&gt;，這樣它就能接收串列的 &lt;code&gt;IEnumerable&amp;lt;ServiceBusMessage&amp;gt;&lt;/code&gt; 了（真貼心啊 Azure）&lt;/p&gt;
&lt;p&gt;不過，我們還可以選擇使用 &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusmessagebatch&#34;&gt;ServiceBusMessageBatch&lt;/a&gt; 這個工具來幫我們一次發他個一批訊息。&lt;/p&gt;
&lt;p&gt;這邊也示範一下。&lt;del&gt;讓我之後可以回來抄&lt;/del&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 將一堆訊息放入佇列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;context&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost(&amp;#34;Batch&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task EnqueueBatch([FromBody] &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 把訊息重複個十次，假裝我們有很多訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; contexts = Enumerable.Repeat(context, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和單則訊息的場合一樣：先建立 Client 及 Sender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusClient(connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; queueName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR QUEUE NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var sender = client.CreateSender(queueName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 從 Sender 來建立一批訊息（類似郵差包的感覺）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var messageBatch = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; sender.CreateMessageBatchAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 將訊息逐一嘗試放到這批訊息中（把信塞到郵差包的感覺）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;foreach&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; text &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; contexts)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusMessage(text);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (messageBatch.TryAddMessage(message) &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Exception(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;放入訊息失敗&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 把整個郵差包丟出去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; sender.SendMessagesAsync(messageBatch);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;直接多十條訊息，看起來沒問題：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/iZxuUqu.webp&#34;
  alt=&#34;Image&#34;width=&#34;249&#34; height=&#34;104&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;傳送物件到佇列&#34;&gt;傳送物件到佇列&lt;/h3&gt;
&lt;p&gt;Ｑ：我要傳的東西不是 string 而是物件怎麼辦？&lt;/p&gt;
&lt;p&gt;Ａ：山不轉路轉，就用 &lt;code&gt;JsonSerializer.Serialize&lt;/code&gt; 轉成 Json 再傳。&lt;/p&gt;
&lt;p&gt;好的結案。&lt;/p&gt;
&lt;h2 id=&#34;從佇列取出訊息&#34;&gt;從佇列取出訊息&lt;/h2&gt;
&lt;p&gt;現在我們已經可以把訊息丟到 Queue 中了，接下來當然就是要拿出來囉！&lt;/p&gt;
&lt;p&gt;拿出來的時候也有幾個不同的姿勢，接著就讓我們一一介紹下：&lt;/p&gt;
&lt;h3 id=&#34;從佇列中讀取一則訊息&#34;&gt;從佇列中讀取一則訊息&lt;/h3&gt;
&lt;p&gt;就像我們要寫入訊息的時候，要從 &lt;code&gt;ServiceBusClient&lt;/code&gt; 建立一個 &lt;code&gt;ServiceBusSender&lt;/code&gt; 一樣，當我們要寄送訊息的時候，也要從 &lt;code&gt;ServiceBusClient&lt;/code&gt; 建立一個 &lt;code&gt;ServiceBusReceiver&lt;/code&gt; 來幫我們處理訊息：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 取出佇列中的單則訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet(&amp;#34;Receive&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt; Receive()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和發送訊息的場合差不多：先建立 Client 及 Receiver&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusClient(connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和前面的 ServiceBusSender 一樣，有提供 DisposeAsync 方法讓我們用完時關閉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 或是直接使用 await using 包起來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; queueName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR QUEUE NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var receiver = client.CreateReceiver(queueName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 使用 ReceiveMessageAsync 來把訊息讀取出來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; receiver.ReceiveMessageAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; body = message.Body.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 告訴 Service Bus 這個訊息有成功處理了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; receiver.CompleteMessageAsync(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; body;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;過程相當的簡單，只需要叫 &lt;code&gt;ServiceBusReceiver&lt;/code&gt; 幫忙拿出來就好。現在讓我們呼叫試試：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/H9v2PdJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;722&#34; height=&#34;613&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;成功取得我們前面放進去的訊息囉！&lt;/p&gt;
&lt;h3 id=&#34;從佇列中持續讀取訊息&#34;&gt;從佇列中持續讀取訊息&lt;/h3&gt;
&lt;p&gt;前面提到的 &lt;code&gt;ServiceBusReceiver&lt;/code&gt; 可以從佇列中取出一則訊息，但大多時候訊息的接收方是被動的，也就說接收方其實並不知道發送方傳訊息了沒、現在有沒有訊息，更不用說主動去取出訊息了。&lt;/p&gt;
&lt;p&gt;因此通常的作法是採用被動接收訊息再進行處理的方式：&lt;strong&gt;事先註冊好處理訊息的事件，當有訊息進來的時候就按照指示去進行處理&lt;/strong&gt;，這時候我們就會需要用到 &lt;code&gt;ServiceBusProcessor&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;並且當 &lt;code&gt;ServiceBusProcessor&lt;/code&gt; 建立之後，我們會需要告訴他兩件事：我們想怎麼處理訊息、出錯的時候該怎麼辦。&lt;/p&gt;
&lt;p&gt;這些都設定完之後，&lt;code&gt;ServiceBusProcessor&lt;/code&gt; 就會上工站崗。只要有訊息進來，它就會按照我們給的小抄去執行&lt;/p&gt;
&lt;p&gt;整理一下，我們接收 Service Queue 的訊息時會需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 ServiceBus 的連線字串建立 &lt;code&gt;ServiceBusClient&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用 Queue 的名稱，從 &lt;code&gt;ServiceBusClient&lt;/code&gt; 建立 &lt;code&gt;ServiceBusProcessor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;設定接收到訊息之後的處理方式 &lt;code&gt;ProcessMessageAsync&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;設定出錯時的處理方式 &lt;code&gt;ProcessErrorAsync&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;讓 &lt;code&gt;ServiceBusProcessor&lt;/code&gt; 持續接收訊息&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 取出佇列中的訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Dequeue()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和發送訊息的場合差不多：先建立 Client 及 Processor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusClient(connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和前面的 ServiceBusSender 一樣，有提供 DisposeAsync 方法讓我們用完時關閉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 或是直接使用 await using 包起來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; queueName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR QUEUE NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var processor = client.CreateProcessor(queueName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 告訴 Processor 我們想怎麼處理訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    processor.ProcessMessageAsync += MessageHandler;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    processor.ProcessErrorAsync += ErrorHandler;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 讓 Processor 上工，開始接收訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; processor.StartProcessingAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 實際上會掛著讓 processor 一直處理送來的訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊示範而已就意思意思跑個一下下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; Task.Delay(&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 讓 Processor 下班休息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; processor.StopProcessingAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 處理佇列訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task MessageHandler(ProcessMessageEventArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 從訊息的 Body 取出我們發送時塞進去的內容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = args.Message.Body.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 對訊息內容做你想做的事&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊就印出來看個一眼意思意思&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Console.WriteLine(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 告訴 Service Bus 這個訊息有成功處理了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; args.CompleteMessageAsync(args.Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 處理佇列錯誤訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task ErrorHandler(ProcessErrorEventArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 從訊息中取出錯誤訊息$$&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; exception = args.Exception.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 對訊息做一些錯誤處理，例如存到日誌系統之類的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊也印出來看個一眼意思意思&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Console.WriteLine(exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就讓我們呼叫看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hKoP1bD.webp&#34;
  alt=&#34;Image&#34;width=&#34;877&#34; height=&#34;421&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見我們確實收到了前面發送的「Hello」，接收大成功！&lt;/p&gt;
&lt;h3 id=&#34;補充在-servicebus-explorer-確認訊息&#34;&gt;補充：在 ServiceBus Explorer 確認訊息&lt;/h3&gt;
&lt;p&gt;除了直接在程式中接收訊息以外，我們在 Azure 的頁面上其實也能確認傳遞中的訊息內容。&lt;/p&gt;
&lt;p&gt;首先到佇列左側的列表找到「&lt;strong&gt;Service Bus Explorer&lt;/strong&gt;」，就可以確認目前佇列和無效信件中的訊息數量。&lt;/p&gt;
&lt;p&gt;點下從頭查看後，就可以看見訊息列表的內容，並點擊訊息查看本文和屬性：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Uy6MDui.webp&#34;
  alt=&#34;Image&#34;width=&#34;1003&#34; height=&#34;668&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在找問題的時候還蠻好用，想確認訊息內容的時候可以試試。&lt;/p&gt;
&lt;h2 id=&#34;建立主題topic&#34;&gt;建立主題（Topic）&lt;/h2&gt;
&lt;p&gt;除了佇列（Queue）以外，Service Bus 還提供了另一種傳輸模式：主題（Topic）&lt;/p&gt;
&lt;p&gt;讓我們看一下微軟把拔提供的主題概念圖，可以和前面佇列的圖做一下比較：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9DwGFgL.webp&#34;
  alt=&#34;Image&#34;width=&#34;780&#34; height=&#34;266&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;簡單來說就是傳送出去之後，會有&lt;strong&gt;多個接收者&lt;/strong&gt;等著收訊息。&lt;/p&gt;
&lt;p&gt;現在就讓我們回到 Azure Service Bus 的頁面，點選上方的「＋主題」來建立新主題吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pHdPOvz.webp&#34;
  alt=&#34;Image&#34;width=&#34;668&#34; height=&#34;375&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：主題（Topic）是標準定價層才提供的功能。如果是使用最便宜的基本定價層的朋友，「＋主題」的按鈕應該會反灰的，這時候就要需要從 Service Bus 概觀頁面的「定價層」變更到標準（Standard）才能建立主題。&lt;/p&gt;
&lt;p&gt;當然不同定價層的價格也會不一樣，要記得確認一下價格呦！&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著就和前面的佇列一樣取個好名字：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/r51t3f8.webp&#34;
  alt=&#34;Image&#34;width=&#34;420&#34; height=&#34;405&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;建立完就會出現在我們的 ServiceBus 服務中囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/kF998IO.webp&#34;
  alt=&#34;Image&#34;width=&#34;423&#34; height=&#34;154&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;和單純一對一的佇列不一樣，我們還會需要替主題建立訂用帳戶（Subscriptions）&lt;/strong&gt;，這樣主題才知道它到底要把訊息送給哪些對象。&lt;/p&gt;
&lt;p&gt;現在讓我們進入主題的頁面，並找到訂用帳戶：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vNJpXfl.webp&#34;
  alt=&#34;Image&#34;width=&#34;881&#34; height=&#34;467&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;進去之後讓我們來新增訂用帳戶：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/wRUeMO2.webp&#34;
  alt=&#34;Image&#34;width=&#34;688&#34; height=&#34;266&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;首先當然是要取個好名字，這邊就先用 Sub1 來當作一號訂閱者的暱稱吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rQvOrlM.webp&#34;
  alt=&#34;Image&#34;width=&#34;608&#34; height=&#34;489&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這邊和佇列的時候一樣，要注意「最大傳遞計數」（Maximum Delivery Count），簡單來說就是這封訊息會嘗試傳遞幾次，如果超過次數都沒有傳遞成功就會被丟到無效信件。可以參閱 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-dead-letter-queues#maximum-delivery-count&#34;&gt;服務匯流排寄不出的信件佇列的概觀&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;另一個要注意的部份是「啟用工作階段」（Session）：簡單來說就是保證訊息的先進先出（FIFO），藉由在訊息中傳遞 SessionID，然後根據訊息的 SessionID 和接收者建立連線之後發送，讓同個工作階段的訊息按照順序發送到同個對象進行處理。&lt;/p&gt;
&lt;p&gt;這功能在佇列和主題都可以使用，但要定價層在標準和以上才支援。當你有多台機器在接收訊息時會對工作階段比較有感覺（例如說 SessionID 寫死然後開了很多台來處理訊息，結果因為工作階段會全部卡在同一台囧）&lt;/p&gt;
&lt;p&gt;詳細可以參閱 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/message-sessions#session-features&#34;&gt;Azure 服務匯流排訊息工作階段&lt;/a&gt;，圖片說明會比較好理解。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;建立完畢之後回到訂用帳戶就會看到囉，這邊為了等等能夠示範傳遞訊息給多個訂用者，所以也順便開了 Sub2：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/q3xkzdQ.webp&#34;
  alt=&#34;Image&#34;width=&#34;439&#34; height=&#34;200&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就完成了主題的建立啦！接著讓我們回到程式中來撰寫訊息吧～&lt;/p&gt;
&lt;h3 id=&#34;將訊息放入主題&#34;&gt;將訊息放入主題&lt;/h3&gt;
&lt;p&gt;基本上來說，訊息放入主題的方式就和放入佇列的方式一樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 將訊息放入主題&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Enqueue([FromBody] &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 ServiceBus 的連線字串建立 Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 連線字串可以在 Azure ServiceBus 頁面的共用存取原則找到&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ServiceBusClient 用完記得要呼叫 DisposeAsync() 來關掉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 或是直接使用 await using 包起來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusClient(connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 傳遞 Topic 的名字給 CreateSender 方法來建立 Sender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和 ServiceBusClient 一樣，有提供 DisposeAsync 方法來關閉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 或是直接使用 await using 包起來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; topicName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR TOPIC NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var sender = client.CreateSender(topicName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 將要傳送的訊息包裝成 ServiceBusMessage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 並使用 ServiceBusSender.SendMessageAsync 傳送出去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusMessage(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; sender.SendMessageAsync(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不用當成大家來找碴，因為真的就是一樣的放法。畢竟 &lt;code&gt;CreateSender&lt;/code&gt; 的參數名稱是這樣的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nvj36PO.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;151&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;事不宜遲，我們馬上就來傳一封訊息試試：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/oiL6TD9.webp&#34;
  alt=&#34;Image&#34;width=&#34;309&#34; height=&#34;330&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這時候再到我們的主題下的訂用帳戶，可以看見訂用帳戶中已經有訊息囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/quyH7n9.webp&#34;
  alt=&#34;Image&#34;width=&#34;896&#34; height=&#34;395&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;因為主題會傳送給所有訂用帳戶，所以兩個訂用帳戶都會分別收到這則訊息，讓我們也確認一眼吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/uoy84gu.webp&#34;
  alt=&#34;Image&#34;width=&#34;908&#34; height=&#34;395&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看來我們很順利地把訊息傳送出去啦！那因為整批放入的方式也和佇列一樣，這邊就不再贅述。接下來就讓我們把訊息取出來試試吧～&lt;/p&gt;
&lt;h3 id=&#34;從主題中讀取訊息&#34;&gt;從主題中讀取訊息&lt;/h3&gt;
&lt;p&gt;就像前面寫入訊息到主題和寫入佇列長得九成像一樣，讀取也是差不多的。最大的差別是在&lt;strong&gt;建立 Processor 時，需要多給訂用帳戶名稱&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 需要同時告訴 Processor 主題名稱和訂用帳戶名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; queueName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR QUEUE NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subscriptionName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Sub1&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var processor = client.CreateProcessor(topicName, subscriptionName);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;讓我們修改成 Topic 名稱以及剛剛的訂用帳戶 Sub1 之後執行看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/udHkpxn.webp&#34;
  alt=&#34;Image&#34;width=&#34;1008&#34; height=&#34;800&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見訂用帳戶 Sub1 的訊息也消耗掉了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lpLy4Rd.webp&#34;
  alt=&#34;Image&#34;width=&#34;650&#34; height=&#34;311&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而訂用帳戶 Sub2 的訊息還在，當我們使用 Sub2 來建立 Processor 並接收訊息之後才會消失：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MvOgIL5.webp&#34;
  alt=&#34;Image&#34;width=&#34;944&#34; height=&#34;389&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其餘的部份都和處理佇列的時候一樣。但為了版面一致 &lt;del&gt;我之後回來複製的時候方便&lt;/del&gt; 這邊還是附上程式碼：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 取出主題中的訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Dequeue()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和發送訊息的場合差不多：先建立 Client 及 Processor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var client = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ServiceBusClient(connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和前面的 ServiceBusSender 一樣，有提供 DisposeAsync 方法讓我們用完時關閉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 或是直接使用 await using 包起來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 和佇列不一樣的是：需要同時告訴 Processor 主題名稱和訂用帳戶名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; topicName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR TOPIC NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; subscriptionName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SUBSCRIPTION NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var processor = client.CreateProcessor(topicName, subscriptionName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 告訴 Processor 我們想怎麼處理訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    processor.ProcessMessageAsync += MessageHandler;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    processor.ProcessErrorAsync += ErrorHandler;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 讓 Processor 上工，開始接收訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; processor.StartProcessingAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 實際上會掛著讓 processor 一直處理送來的訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊就意思意思跑個一下下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; Task.Delay(&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 讓 Processor 下班休息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; processor.StopProcessingAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 處理主題訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task MessageHandler(ProcessMessageEventArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 從訊息的 Body 取出我們發送時塞進去的內容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = args.Message.Body.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 對訊息內容做你想做的事。這邊就印出來看個一眼意思意思&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Console.WriteLine(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 告訴 Service Bus 這個訊息有成功處理了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; args.CompleteMessageAsync(args.Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 處理主題錯誤訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task ErrorHandler(ProcessErrorEventArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 從訊息中取出錯誤訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; exception = args.Exception.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 對訊息做錯誤處理，例如存到日誌系統之類的。這邊也印出來看個一眼意思意思&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Console.WriteLine(exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;稍作整理&#34;&gt;稍作整理&lt;/h2&gt;
&lt;p&gt;我們已經介紹完了 ServiceBus 的兩種主要工具：佇列和主題的基本操作。&lt;/p&gt;
&lt;p&gt;接下來我將稍微對現在的範例程式碼做點簡單的整理，給有興趣的朋友參考。&lt;/p&gt;
&lt;p&gt;其餘的朋友可以直接跳到最後的&lt;a href=&#34;#%E5%B0%8F%E7%B5%90&#34;&gt;小結&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;使用-iazureclientfactory-搭配依賴注入來建立-azure-client&#34;&gt;使用 IAzureClientFactory 搭配依賴注入來建立 Azure Client&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;這一小節會用到 .Net Core 的依賴注入，還沒有概念的朋友可以參照 &lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在範例中我們總是直接 &lt;code&gt;new ServiceBusClient&lt;/code&gt;。但根據&lt;a href=&#34;https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-performance-improvements?tabs=net-standard-sdk-2#reusing-factories-and-clients&#34;&gt;官方建議&lt;/a&gt;，ServiceBusClient 應該只建立一份並重複使用，以避免重新連線之類的效能損失。&lt;/p&gt;
&lt;p&gt;針對 &lt;code&gt;ServiceBusClient&lt;/code&gt; 的建立和管理，我們可以使用 &lt;code&gt;IAzureClientFactory&lt;/code&gt; 來讓它自動控制，替我們管理 Client 的實例和連線，用起來就像 &lt;code&gt;HttpClientFactory&lt;/code&gt; 一樣。&lt;/p&gt;
&lt;p&gt;要使用 &lt;code&gt;IAzureClientFactory&lt;/code&gt;，我們需要先安裝 &lt;code&gt;Microsoft.Extensions.Azure&lt;/code&gt; 套件：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vvFZGCE.webp&#34;
  alt=&#34;Image&#34;width=&#34;541&#34; height=&#34;170&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在讓我們移動到 &lt;code&gt;Program.cs&lt;/code&gt; 並加上 ServiceBusClient 的註冊：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddAzureClients(clientsBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    clientsBuilder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .AddServiceBusClient(connectionString)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .WithName(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ServiceBusClient&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看起來應該會像這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Uo0IR1g.webp&#34;
  alt=&#34;Image&#34;width=&#34;737&#34; height=&#34;509&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：示範專案是使用 .Net 6。如果是使用其他版本或其他 DI 工具的朋友，請再按照自己的狀況調整吧！&lt;/p&gt;
&lt;p&gt;例如在 &lt;code&gt;HostBuilder&lt;/code&gt; 的場合，可能就要在 &lt;code&gt;ConfigureServices&lt;/code&gt; 中進行配置等等，只能祝各位好運。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;註：除了 &lt;code&gt;WithName&lt;/code&gt; 替 Client 實例取名以外，也可以呼叫 &lt;code&gt;ConfigureOptions&lt;/code&gt; 來對 ServiceBusClient 進行各式各樣的設定&lt;/p&gt;
&lt;p&gt;關於 ServiceBusClient 的設定內容可以參閱 &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/api/azure.messaging.servicebus.servicebusclientoptions&#34;&gt;ServiceBusClientOptions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;而關於 AddAzureClients 的註冊則可以參閱 &lt;a href=&#34;https://stackoverflow.com/questions/68688838/how-to-register-servicebusclient-for-dependency-injection&#34;&gt;How to register ServiceBusClient for dependency injection? - stackoverflow&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在我們註冊完了。接著只需要回到使用的類別讓 &lt;code&gt;IAzureClientFactory&lt;/code&gt; 注入進來就可以囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// Service Bus Queue + IAzureClientFactory 示範用 Controller&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;QueueWithDiController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ServiceBusClient _serviceBusClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; QueueWithDiController(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IAzureClientFactory&amp;lt;ServiceBusClient&amp;gt; azureClientFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 使用注入進來的 IAzureClientFactory 來建立 ServiceBusClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _serviceBusClient = azureClientFactory.CreateClient(name: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ServiceBusClient&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們就可以把原本範例中的呼叫也改成使用這個 Client 來操作囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; queueName = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR QUEUE NAME&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; var sender = _serviceBusClient.CreateSender(queueName);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;註：在前面的&lt;a href=&#34;https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-performance-improvements?tabs=net-standard-sdk-2#reusing-factories-and-clients&#34;&gt;官方建議&lt;/a&gt;中，除了 &lt;code&gt;ServiceBusClient&lt;/code&gt; 以外，其實也建議 &lt;code&gt;ServiceBusSender&lt;/code&gt;、&lt;code&gt;ServiceBusReceiver&lt;/code&gt;、&lt;code&gt;ServiceBusProcessor&lt;/code&gt; 這幾個也應該要維持單例，保持和伺服器的連線以減少建立連線的效能和時間損失。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ServiceBusClient&lt;/code&gt; 已經藉由前面提到的 &lt;code&gt;IAzureClientFactory&lt;/code&gt; 來解決；而像是 &lt;code&gt;ServiceBusSender&lt;/code&gt; 的後面三項，我們就需要根據專案的狀況來規劃如何重複使用。&lt;/p&gt;
&lt;p&gt;例如只需要一個 Sender 的場合，我們可以直接在注入的時候註冊成單例；而要和多個 Queue 連線所以需要多個 Sender 的時候，就可以考慮建立一個工廠來管理，並且在工廠裡面優先返回已經建好的實例等等。這部份就請再各位朋友保持柔軟的彈性來處理。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;將連線字串抽取到-config&#34;&gt;將連線字串抽取到 Config&lt;/h3&gt;
&lt;p&gt;把鏡頭回到我們註冊的地方：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddAzureClients(clientsBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    clientsBuilder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .AddServiceBusClient(connectionString)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .WithName(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ServiceBusClient&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到現在的連線字串是寫死在 &lt;code&gt;Program.cs&lt;/code&gt; 中的（即使是前面的範例也是直接寫死在 Controller）&lt;/p&gt;
&lt;p&gt;但實務上我們大多會將連線字串放在設定檔，例如 Config 或 Appsettings，方便在正式環境或是 Azure 服務上運行時，能夠由外部去設定 or 變更組態來置換連線字串。因此這邊也應該要改成從組態中進行讀取。&lt;/p&gt;
&lt;p&gt;首先讓我們把連線字串丟到 &lt;code&gt;appsettings.json&lt;/code&gt; 的 &lt;code&gt;ConnectionStrings&lt;/code&gt; 區塊：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ConnectionStrings&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;ServiceBus&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;YOUR SERVICE BUS CONNECTION STRING&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那麼我們就可以更彈性靈活地使用連線字串來註冊 Client 囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddAzureClients(clientsBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = builder.Configuration
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .GetConnectionString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ServiceBus&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    clientsBuilder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .AddServiceBusClient(connectionString)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .WithName(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ServiceBusClient&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;這篇我們稍微紀錄了訊息佇列（Message Queue）的用途：拆分生產者和消費者的直接依賴，在中間架設佇列來傳遞訊息。以及這樣做的幾個好處：解除耦合、提高擴展、非同步和提供了緩衝區。&lt;/p&gt;
&lt;p&gt;接著介紹了 Azure 的訊息佇列服務：Azure Service Bus，並對其中的兩種模式：佇列和主題，各做了簡單的操作範例。&lt;/p&gt;
&lt;p&gt;最後補充了一些範例能優化的方向；使用 IAzureClientFactory 來注入 Client、保持 Sender 等連線重複使用，以及將連線字串拆出到組態處理。&lt;/p&gt;
&lt;p&gt;當然還有一些進階的使用場景，例如&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-transactions&#34;&gt;交易處理&lt;/a&gt;、&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-geo-dr&#34;&gt;異地複寫&lt;/a&gt;等等，但因為這邊還沒有接觸過，就交給需要深入了解的朋朋自行研究囉。&lt;/p&gt;
&lt;p&gt;這算是我第一次接觸 Azure 相關的工具，加減筆記一下簡單的使用場景（傳入訊息到佇列／從佇列取出訊息），範例也已經丟到 &lt;a href=&#34;https://github.com/Igouist/Demo.AzureServiceBus&#34;&gt;Github&lt;/a&gt; 上囉，有缺漏的也歡迎各位朋朋幫忙補充，感謝感謝。&lt;/p&gt;
&lt;p&gt;那麼今天的紀錄就到這邊囉，希望以後還能回來抄。&lt;del&gt;總不會筆記寫完就要改用 RabbitMQ 了吧囧&lt;/del&gt;&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.dotblogs.com.tw/supershowwei/2022/02/13/221639&#34;&gt;[食譜好菜] 比 Azure Queue Storage 功能更完整的 Message Queue 服務 - Azure Service Bus | 軟體主廚的程式料理廚房 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10240878&#34;&gt;訊息服務站 - ServiceBus - iT 邦幫忙：：一起幫忙解決難題，拯救 IT 人的一天 (ithome.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-dotnet-get-started-with-queues&#34;&gt;開始使用 Azure 服務匯流排佇列 (.NET) - Azure Service Bus | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/MicrosoftDocs/azure-docs.zh-tw/blob/master/articles/service-bus-messaging/service-bus-dotnet-get-started-with-queues.md&#34;&gt;在 Azure 服務匯流排佇列 (.NET) 中傳送和接收訊息 (github.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10161086&#34;&gt;使用 Python 操作 Azure Service Bus Queues - iT 邦幫忙 (ithome.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10161209&#34;&gt;使用 Python 操作 Service Bus Topics/Subscriptions - iT 邦幫忙 (ithome.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/AllenMaster/p/14000933.html&#34;&gt;Azure Service Bus（一）入门简介 - Grant_Allen - 博客园 (cnblogs.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/AceLee/2019/07/18/195448&#34;&gt;建立 Azure Service Bus | 程式碼學習不歸路 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/AceLee/2019/07/18/205733&#34;&gt;透過 Service Bus Queue trigger Azure Function | 程式碼學習不歸路 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/service-bus-messaging/service-bus-azure-and-service-bus-queues-compared-contrasted&#34;&gt;比較 Azure 佇列儲存體和服務匯流排佇列 - Azure Service Bus | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/event-grid/compare-messaging-services&#34;&gt;比較 Azure 傳訊服務 - Azure Event Grid | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Snispate —— 方便的截圖小幫手，放下剪取工具和小畫家吧</title>
      <link>https://igouist.github.io/post/2022/08/snispate/</link>
      <pubDate>Sun, 07 Aug 2022 11:44:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/08/snispate/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/w0SORqB.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;600&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;嗨各位朋朋，又到了「同事推薦的好用工具」時間！&lt;/p&gt;
&lt;p&gt;今天要推薦的是 &lt;a href=&#34;https://apps.microsoft.com/store/detail/snipaste/9P1WXPKB68KX?hl=zh-tw&amp;amp;gl=TW&#34;&gt;Snipaste&lt;/a&gt; 這套香香的截圖工具。&lt;/p&gt;
&lt;p&gt;在遠古時代的時候，我寫部落格或是測ＡＰＩ要貼圖附結果時，都是使用 Windows 內建的剪取工具（&lt;code&gt;Shift + Win + S&lt;/code&gt;）來螢幕截圖，之後貼到小畫家上再進行標記（例如畫底線、紅色框框等等）&lt;/p&gt;
&lt;p&gt;但有了 Snispate，這個動作就可以一氣呵成！&lt;/p&gt;
&lt;p&gt;Snipaste 可以直接按下 F1 進入截圖，這時候我們能用滑鼠拖曳來選取截圖範圍，也能夠用拖拉邊界、鍵盤慢慢移動一像素的方式來調整截圖範圍&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/kQN8yzR.webp&#34;
  alt=&#34;Image&#34;width=&#34;641&#34; height=&#34;676&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其中最方便的部份，就是滑鼠點擊某個區塊時，Snipaste 會嘗試幫你偵測該區塊的範圍，例如直接選取某個視窗，在截圖某些網頁的時候相當方便。此外也還有重複選取上次截圖範圍等貼心功能：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UJXahha.webp&#34;
  alt=&#34;Image&#34;width=&#34;279&#34; height=&#34;187&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;選取截圖範圍之後，就會跳出一排工具列：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yrbv0HT.webp&#34;
  alt=&#34;Image&#34;width=&#34;803&#34; height=&#34;681&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這時候我們就可以直接對截圖內容做標記和加註，例如用框框把重點框選起來，直接加入文字方塊說明，甚至把一些機密資訊馬賽克一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/51XAXWG.webp&#34;
  alt=&#34;Image&#34;width=&#34;500&#34; height=&#34;333&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而編輯好了之後，就可以選擇最右邊的複製到剪貼簿或存檔囉！&lt;/p&gt;
&lt;p&gt;像我個人截圖時幾乎都是為了貼到別的地方，所以就時常使用 Snispate 直接 &lt;strong&gt;截圖 → 標記 → 複製 → 貼上&lt;/strong&gt;，一氣呵成！&lt;/p&gt;
&lt;p&gt;此外要特別講一下的就是複製左邊的這個&lt;strong&gt;釘選圖片&lt;/strong&gt;，它可以讓你把現在的截圖釘選在螢幕上：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Z0JuAgk.webp&#34;
  alt=&#34;Image&#34;width=&#34;1538&#34; height=&#34;755&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有些時候我們可能正在整理資料，這時候就可以先用釘選的方式把截圖都卡在螢幕上，方便我們快速整合。&lt;/p&gt;
&lt;p&gt;這個釘選功能用起來，就會像一些警探／偵探片追查犯人時的牆壁和白板那樣，把照片和資料直接釘在上面看出關聯那樣 &lt;del&gt;用起來還蠻帥的&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;截圖軟體其實還蠻多套的，Snipaste 讓我用起來感覺相當順手，並且釘選貼圖功能也讓有多螢幕的我在整合資料的時候更加流暢。有興趣的朋友也可以試試看，這邊附上 Windows Store 載點：&lt;a href=&#34;https://apps.microsoft.com/store/detail/snipaste/9P1WXPKB68KX?hl=zh-tw&amp;amp;gl=TW&#34;&gt;Snipaste&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;參考資料&#34;&gt;參考資料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://zh.snipaste.com/&#34;&gt;Snipaste&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2016/07/snipaste.html&#34;&gt;不只是優秀截圖軟體！ Snipaste 用貼圖創造多工神器 - 電腦玩物&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.xiaoyao.tw/2017/06/snipaste.html&#34;&gt;Snipaste 超乎想像的截圖軟體，桌面就是編輯區 - 逍遙の窩&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>使用 Wox &amp; Everything 在 Windows 上得到良好的搜尋體驗</title>
      <link>https://igouist.github.io/post/2022/06/wox-and-everything/</link>
      <pubDate>Sat, 18 Jun 2022 20:11:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/06/wox-and-everything/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Qw6TXg6.webp&#34;
  alt=&#34;Image&#34;width=&#34;1089&#34; height=&#34;445&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;介紹一下同事推薦的 Windows 好用工具：方便的快速啟動工具 Wox 以及能快速搜尋檔案的 Everything。&lt;/p&gt;
&lt;p&gt;如果你曾經有看著檔案總管轉圈圈、等到火都上來了的經驗；或是懶得伸伸手用滑鼠點資料夾，那也許你能試試 Wox + Everything 的組合來稍稍拯救你的心理健康。&lt;/p&gt;
&lt;h2 id=&#34;everything&#34;&gt;Everything&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://www.voidtools.com/&#34;&gt;Everything&lt;/a&gt; 是一款簡單的搜尋工具，它會抓取系統裡的檔案名稱來建立索引&lt;/strong&gt;，因此搜尋的時候並不是像檔案總管即時整個海撈，而是直接抓取 Everything 做好的檔名索引，搜尋速度當然就會快上許多。&lt;/p&gt;
&lt;p&gt;當然，如果不是想找檔案名稱的時候就沒辦法囉。但大多數找檔案時的搜尋場景都是檔案名稱，所以 Everything&lt;/p&gt;
&lt;p&gt;總之到&lt;a href=&#34;https://www.voidtools.com/&#34;&gt;下載頁面&lt;/a&gt;把對應的安裝檔抓下來後，就是慣例的下一步下一步式安裝。&lt;/p&gt;
&lt;p&gt;安裝完畢之後打開，應該就會看到一個相當簡潔的畫面：上面是搜尋欄，下面是搜尋結果，支援多關鍵字。就是這麼簡單！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VsD1rDG.webp&#34;
  alt=&#34;Image&#34;width=&#34;780&#34; height=&#34;657&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;wox&#34;&gt;Wox&lt;/h2&gt;
&lt;p&gt;要發揮 Everything 的力量，就需要搭配他的好朋友 Wox！&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.wox.one/&#34;&gt;Wox&lt;/a&gt; 是一款搜尋列工具，基本上就是能讓你用快捷鍵就呼叫出一個搜尋欄來使用。如果有在隔壁棚 Mac 看過 Spotlight 的朋友可能會比較有概念。&lt;/p&gt;
&lt;p&gt;一樣讓我們到&lt;a href=&#34;http://www.wox.one/&#34;&gt;下載頁面&lt;/a&gt;把 Wox 載回來並安裝。&lt;/p&gt;
&lt;p&gt;安裝完畢之後，使用 &lt;code&gt;Alt + 空白鍵&lt;/code&gt; 的快捷鍵，就可以叫出 Wox：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Q6geVPn.webp&#34;
  alt=&#34;Image&#34;width=&#34;800&#34; height=&#34;376&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了和 Everything 結合來搜尋檔案以外，已經安裝的應用程式也是沒問題的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gPFfxQ3.webp&#34;
  alt=&#34;Image&#34;width=&#34;800&#34; height=&#34;376&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除此之外，Wox 還支援安裝模組來讓搜尋框更好用！&lt;/p&gt;
&lt;p&gt;讓我們對運行中的 Wox 右鍵進入設定選單：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/d8Nw70J.webp&#34;
  alt=&#34;Image&#34;width=&#34;145&#34; height=&#34;67&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;設定中可以調整主題等等：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/setgxZx.webp&#34;
  alt=&#34;Image&#34;width=&#34;786&#34; height=&#34;593&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著我們來看看模組，可以看到已經安裝了一些模組，例如網頁搜尋和命令列指令：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rzeEr0t.webp&#34;
  alt=&#34;Image&#34;width=&#34;786&#34; height=&#34;593&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;大多數的模組也可以自訂觸發關鍵字。例如網頁搜尋的觸發關鍵字 &lt;code&gt;g&lt;/code&gt;，就可以這樣使用：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ghxyyPz.webp&#34;
  alt=&#34;Image&#34;width=&#34;800&#34; height=&#34;126&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;但我最常用到這功能的場景還是 Code 噴 Error 的時候，直接 &lt;code&gt;Alt + Space&lt;/code&gt; 叫出 Wox 然後 &lt;code&gt;g&lt;/code&gt; 貼上錯誤訊息啦 XD&lt;/p&gt;
&lt;p&gt;Wox 搭配人人都會的 &lt;code&gt;Alt + Tab&lt;/code&gt; 起來用，切換視窗／檔案也算是相當方便了，不過其實會安裝這些工具，說穿了就是手懶得離開鍵盤嘛哈哈&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/weixin_46098577/article/details/121489645&#34;&gt;Wox + Everything = 效率神器 - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2016/08/wox-windows.html&#34;&gt;Wox 可用外掛強化的 Windows 快捷啟動列，免費開源 (playpcesor.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 使用 AngleSharp 爬蟲工具來抓取網頁內容吧</title>
      <link>https://igouist.github.io/post/2022/06/angle-sharp/</link>
      <pubDate>Sun, 05 Jun 2022 00:21:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/06/angle-sharp/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DHoF8Yw.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;400&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;前一次用到 AngleSharp 已經是去年抓網路小說的時候，想不到最近又用上了，乾脆就來筆記一下。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://anglesharp.github.io/&#34;&gt;AngleSharp&lt;/a&gt; 是一款簡單方便的 C# 爬蟲套件&lt;/strong&gt;，撈網頁時支援 &lt;a href=&#34;https://developer.mozilla.org/zh-TW/docs/Web/API/Document/querySelector&#34;&gt;QuerySelector&lt;/a&gt; 的語法來篩選網頁元素，並且撈回來的資料集合也都能用 Linq 操作，讓我們能對爬取的網頁內容快速進行篩選和處理，只需要短短的語法就可以開心抓想要的內容。&lt;/p&gt;
&lt;p&gt;說到要示範爬蟲，果然還是要用爬蟲界默認的經典範例 &lt;a href=&#34;https://www.ptt.cc/bbs/Beauty/index.html&#34;&gt;PTT 表特版&lt;/a&gt; 來操作（？），接著就讓我們來寫一個簡單的腳本來抓取文章吧！&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝套件&#34;&gt;安裝套件&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#建立-browser-抓取網頁內容&#34;&gt;建立 Browser 抓取網頁內容&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#調整配置準備-cookie&#34;&gt;調整配置、準備 Cookie&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用篩選器來抓指定的內容&#34;&gt;使用篩選器來抓指定的內容&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#整理抓取到的內容&#34;&gt;整理抓取到的內容&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#搭配遞迴來多撈幾頁&#34;&gt;搭配遞迴來多撈幾頁&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;安裝套件&#34;&gt;安裝套件&lt;/h2&gt;
&lt;p&gt;首先要先從 &lt;a href=&#34;https://www.nuget.org/packages/AngleSharp&#34;&gt;Nuget&lt;/a&gt; 安裝 AngleSharp&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JeS3P2J.webp&#34;
  alt=&#34;Image&#34;width=&#34;1002&#34; height=&#34;310&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;確認安裝完畢後就可以開始撰寫囉～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本篇接下來會使用 Linqpad 來進行簡單的範例，使用 VisualStudio 的朋友遇到 &lt;code&gt;Dump()&lt;/code&gt; 之類的語法就請再自己調整一下呦&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;建立-browser-抓取網頁內容&#34;&gt;建立 Browser 抓取網頁內容&lt;/h2&gt;
&lt;p&gt;首先我們需要先建立一個 Browser 來代替我們做事：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 建立 Browser 的配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = AngleSharp.Configuration.Default.WithDefaultLoader();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 根據配置建立出我們的 Browser &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; browser = BrowsingContext.New(config);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;建立之後就可以&lt;strong&gt;用這個 browser 的 &lt;code&gt;OpenAsync()&lt;/code&gt; 來抓取網頁內容囉&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 這邊為了方便處理也順便把 `Main` 改成非同步的版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = AngleSharp.Configuration.Default.WithDefaultLoader();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; browser = BrowsingContext.New(config);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊用的型別是 AngleSharp 提供的 AngleSharp.Dom.Url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Url(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://www.ptt.cc/bbs/Beauty/index.html&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 使用 OpenAsync 來打開網頁抓回內容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; document = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; browser.OpenAsync(url);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	document.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到抓了一堆東西回來，包含網頁的 Uri、Body 等等：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/EfJewBi.webp&#34;
  alt=&#34;Image&#34;width=&#34;709&#34; height=&#34;491&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;調整配置準備-cookie&#34;&gt;調整配置、準備 Cookie&lt;/h2&gt;
&lt;p&gt;在前一個步驟我們雖然把網頁抓回來了，但讓我們看一下 &lt;code&gt;context.Body.InnerHtml&lt;/code&gt; 的內容：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VTbpoOU.webp&#34;
  alt=&#34;Image&#34;width=&#34;1310&#34; height=&#34;642&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看起來跟我們要的文章列表有點差距啊！這是因為 PTT 會先跳出一個視窗詢問是否滿十八歲，選擇「是」之後會記錄一筆 &lt;code&gt;over18=1&lt;/code&gt; 到 Cookie 中，只有持有這個 Cookie 才能進入到文章列表。&lt;/p&gt;
&lt;p&gt;因此我們現在要先把答案準備到 Cookie 裡，這時候我們就需要調整一下前面的配置內容。&lt;/p&gt;
&lt;p&gt;首先先在配置裡加上預設的 Cookies：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 加上 `WithDefaultCookies()` 來加上預設的 Cookie&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = AngleSharp.Configuration.Default
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .WithDefaultLoader()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .WithDefaultCookies();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; browser = BrowsingContext.New(config);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著在我們開啟網頁之前，&lt;strong&gt;使用 &lt;code&gt;SetCookie&lt;/code&gt; 對目標指定要用的 Cookie&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Url(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://www.ptt.cc/bbs/Beauty/index.html&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 加上已滿十八歲的 Cookie 來通過年齡驗證頁面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;browser.SetCookie(url, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;over18=1&amp;#39;&amp;#34;&lt;/span&gt;); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; document = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; browser.OpenAsync(url);	
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在會長得像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = AngleSharp.Configuration.Default
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		.WithDefaultLoader()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		.WithDefaultCookies();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; browser = BrowsingContext.New(config);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Url(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://www.ptt.cc/bbs/Beauty/index.html&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	browser.SetCookie(url, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;over18=1&amp;#39;&amp;#34;&lt;/span&gt;); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; document = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; browser.OpenAsync(url);	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	document.Body.InnerHtml.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們來確認爬回來的 HTML：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hRUlaz3.webp&#34;
  alt=&#34;Image&#34;width=&#34;671&#34; height=&#34;440&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到我們成功進到看板囉！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：另一個很常在配置處理的是使用 LoaderOptions 加上 AngleSharp.Css 提供的 &lt;code&gt;WithCss()&lt;/code&gt; 來抓取 CSS 處理後的結果，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Configuration.Default
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .WithDefaultLoader(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; LoaderOptions 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IsResourceLoadingEnabled = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .WithCss();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不過這些場景（需要掛 Cookie 啦、要先等 CSS 啦）通常都是遇到之後才去 Google 的，這邊就不再贅述。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;註：&lt;code&gt;browser.OpenAsync&lt;/code&gt; 除了提供網址讓他直接抓回來以外，有時候我們也會遇到本機已經有 Html 檔案要處理，或是已經用 HttpClient 等方法把網頁內容拿回來了的狀況&lt;/p&gt;
&lt;p&gt;這種時候也可以用 &lt;code&gt;OpenAsync&lt;/code&gt; 提供的委派方法來把 HTML 字串讀取成 AngleSharp 的物件。例如：&lt;code&gt;OpenAsync(res =&amp;gt; res.Content(html))&lt;/code&gt;，同時委派中的 &lt;code&gt;VirtualResponse&lt;/code&gt; 也提供了對這物件設定 Cookie 等資訊的方法，有這個需求的朋友可以再動手試試。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;使用篩選器來抓指定的內容&#34;&gt;使用篩選器來抓指定的內容&lt;/h2&gt;
&lt;p&gt;現在我們已經成功把網頁內容抓回來了，接著就是要&lt;strong&gt;使用&lt;a href=&#34;https://www.runoob.com/cssref/css-selectors.html&#34;&gt;選擇器&lt;/a&gt;來抓出我們想要的內容囉&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果有用過 JQuery 或是整天寫 CSS 的朋友應該不會陌生，大致上是這樣的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;div&lt;/code&gt;: 就是抓 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#wow&lt;/code&gt;: 抓 id 是 wow 的元素&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.hello&lt;/code&gt;: 抓 class 是 hello 的元素&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;當然也可以加以組合，例如 &lt;code&gt;div#wow&lt;/code&gt;, &lt;code&gt;div &amp;gt; p.hello&lt;/code&gt; 等，有興趣的朋友可以再看一眼 &lt;a href=&#34;https://www.runoob.com/cssref/css-selectors.html&#34;&gt;菜鳥教程的 CSS 選擇器說明&lt;/a&gt;，其他狀況就等需要的時候再查表即可。&lt;/p&gt;
&lt;p&gt;首先讓我們好好觀察 HTML 結構，可以發現標題框是放在 &lt;code&gt;class=&amp;quot;r-ent&amp;quot;&lt;/code&gt; 的 div 裡，那我們就可以這樣下 Selector：&lt;code&gt;div.r-ent&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;接著我們就能用 &lt;code&gt;QuerySelectorAll()&lt;/code&gt; 這個方法來用 Selector 抓取我們想要的元素&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;document
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .QuerySelectorAll(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.r-ent&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// 指定 class 為 r-ent 的 div&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .Select(node =&amp;gt; node.InnerHtml) &lt;span style=&#34;color:#75715e&#34;&gt;// 直接抓內容出來看看&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .Dump();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GIaNgMI.webp&#34;
  alt=&#34;Image&#34;width=&#34;766&#34; height=&#34;698&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到我們成功抓了一排標題資訊回來囉！&lt;/p&gt;
&lt;p&gt;當然 AngleSharp 也提供了 &lt;code&gt;QuerySelector&lt;/code&gt; 來抓取單個元素，這兩個方法用起來和 &lt;a href=&#34;https://developer.mozilla.org/zh-TW/docs/Web/API/Document/querySelector&#34;&gt;JavaScript&lt;/a&gt; 的體驗應該是差不多啦。&lt;/p&gt;
&lt;p&gt;確定我們有把要的內容抓回來，也不會再用到網頁內容的話，就可以補一行 &lt;code&gt;document.Close();&lt;/code&gt; 把開啟的網頁內容順手清掉囉。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：現在都２０２２年了，瀏覽器當然也有提供直接抓 Selector 的功能了&lt;/p&gt;
&lt;p&gt;這邊以 Edge 為例，讓我們到目標網頁按下Ｆ１２打開開發人員工具，直接選取目標：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/F2XyfEX.webp&#34;
  alt=&#34;Image&#34;width=&#34;1916&#34; height=&#34;933&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;選取目標後就會告訴我們這是哪個元素，我們只需要對該元素右鍵，然後複製它的 Selector 就行啦&lt;/p&gt;
&lt;p&gt;不過這樣抓到的 Selector 語法通常會比較囉嗦一點，例如這個頁面就是 &lt;code&gt;#main-container &amp;gt; div.r-list-container.action-bar-margin.bbs-screen&lt;/code&gt;，這部份就再自己調整一下囉&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;整理抓取到的內容&#34;&gt;整理抓取到的內容&lt;/h2&gt;
&lt;p&gt;現在讓我們建立一個類別用來處理標題資訊吧，這邊我只需要名稱、推噓數和文章連結，其他像是作者什麼的不太需要：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Post&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Title { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Push { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Link { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就來把我們剛剛抓到的每一則文章標題轉換成我們要的物件吧！&lt;/p&gt;
&lt;p&gt;觀察上面抓到的標題資訊，可以發現標題的文字和文章連結都放在 &lt;code&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;&lt;/code&gt; 裡的 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;，這邊我們就可以利用 QuerySelector 來抓到這個元素&lt;/p&gt;
&lt;p&gt;而為了拿到連結，這邊會需要&lt;strong&gt;使用 &lt;code&gt;GetAttribute&lt;/code&gt; 來抓取元素的 &lt;code&gt;href&lt;/code&gt; 屬性&lt;/strong&gt;。這樣標題和連結就搞定了。&lt;/p&gt;
&lt;p&gt;另外要注意的是：如果文章被刪掉了，可是抓不到這些東西的！所以可以在 &lt;code&gt;QuerySelector&lt;/code&gt; 之後用 &lt;code&gt;?.&lt;/code&gt; 的方式來做個 Null 時的防呆，最後也可以再用 &lt;code&gt;Where&lt;/code&gt; 來過濾掉無效的文章。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; titleElement = post.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.title &amp;gt; a&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = titleElement?.InnerHtml; &lt;span style=&#34;color:#75715e&#34;&gt;// 標題文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; link = titleElement?.GetAttribute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;href&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 文章連結&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;剩下的推噓數可以看到是放在 &lt;code&gt;&amp;lt;div class=&amp;quot;nrec&amp;quot;&amp;gt;&lt;/code&gt; 裡面，這邊我希望能轉換成數字，方便我們後續如果要用推噓數做篩選。所以讓我們額外處理一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只顯示「爆」，這時候我們就視作 100&lt;/li&gt;
&lt;li&gt;如果有明確的數字，轉換為 Int&lt;/li&gt;
&lt;li&gt;沒有數字的話就當成 0。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pushString = post.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.nrec &amp;gt; span&amp;#34;&lt;/span&gt;)?.InnerHtml;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pushCount = 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pushString == &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;爆&amp;#34;&lt;/span&gt; ? &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt; : 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Int16.TryParse(pushString, &lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; push) ? push : &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後就把這些資訊拿來組裝我們的物件，那麼現在的程式碼就會像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; posts = postSource.Select(post =&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; titleElement = post.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.title &amp;gt; a&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = titleElement?.InnerHtml;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; link = titleElement?.GetAttribute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;href&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pushString = post.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.nrec &amp;gt; span&amp;#34;&lt;/span&gt;)?.InnerHtml;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pushCount = 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pushString == &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;爆&amp;#34;&lt;/span&gt; ? &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt; : 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Int16.TryParse(pushString, &lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; push) ? push : &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Post
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Title = title,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Link = link,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Push = pushCount
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.Where(post =&amp;gt; post.Title != &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZnjBF3U.webp&#34;
  alt=&#34;Image&#34;width=&#34;590&#34; height=&#34;234&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;到這邊我們就成功把網頁的內容抓下來、篩選出我們要的內容囉！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：除了我們前面使用的 QuerySelector、GetAttribute 以外，AngleSharp 還提供了 &lt;code&gt;GetElementsByTagName&lt;/code&gt;、&lt;code&gt;Children&lt;/code&gt; 等屬性和方法讓我們能方便地在 DOM 中到處抓取元素。&lt;/p&gt;
&lt;p&gt;有興趣的朋友再自己摸索一下吧，我自己是比較習慣無腦 &lt;code&gt;QuerySelector&lt;/code&gt; 了啦哈哈。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;搭配遞迴來多撈幾頁&#34;&gt;搭配遞迴來多撈幾頁&lt;/h2&gt;
&lt;p&gt;其實我們前面已經把 AngleSharp 的基本操作跑完一輪了，基本上不外乎是「&lt;strong&gt;打開目標網頁 → 找到目標元素 → 用篩選器抓出來&lt;/strong&gt;」這樣的 Loop，這節只是單純讓這個腳本完善一點而已。&lt;/p&gt;
&lt;p&gt;因此不感興趣的朋友也可以直接跳過這一段，直接前往&lt;a href=&#34;#%E5%B0%8F%E7%B5%90&#34;&gt;小結&lt;/a&gt;，準備出發去動手抓自己想要的網頁囉。&lt;/p&gt;
&lt;p&gt;現在讓我們回到文章列表來，只抓第一頁實在沒什麼搞頭。如果我們想要換頁，那麼首先就要先抓出這個換頁按鈕：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ndkeqwG.webp&#34;
  alt=&#34;Image&#34;width=&#34;371&#34; height=&#34;46&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;利用前面提到的Ｆ１２大法，我們可以拿到這個按鈕的 Selector 語法，讓我們直接丟到 &lt;code&gt;QuerySelector&lt;/code&gt; 裡，並且取得它的 &lt;code&gt;href&lt;/code&gt; 屬性：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; nextPageLink = document
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.btn-group.btn-group-paging &amp;gt; a:nth-child(2)&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.GetAttribute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;href&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// = /bbs/Beauty/index3980.html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在我們有了往前一頁的連結了，後續只需要把站台的 Url 和下一頁的相對 Url 就可以拼湊出換頁的連結了。&lt;/p&gt;
&lt;p&gt;如此一來就可以做出「抓文章 → 下一頁 → 抓文章…」的循環。像這種場合直接使用遞迴，寫起來會快一點。&lt;/p&gt;
&lt;p&gt;首先讓我們把抓取文章的處理過程抽出去當作方法，當然會重複用到我們的 Browser，還有站台 Url 以及每一頁的 Url，最後再丟個數字控制要抓幾頁，大概像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;IEnumerable&amp;lt;Post&amp;gt;&amp;gt; GetPosts(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	IBrowsingContext browser,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; baseUrl, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; pageUrl,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; remainingPages)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在讓我們把上面的處理步驟逐一搬移到方法中，預期會需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先組裝 Url、設定 Cookie 然後 Open 抓回網頁內容&lt;/li&gt;
&lt;li&gt;抓取這一頁的所有文章標題&lt;/li&gt;
&lt;li&gt;取得下一頁的連結&lt;/li&gt;
&lt;li&gt;遞迴取得下一頁及往後頁數的文章列表&lt;/li&gt;
&lt;li&gt;把這一頁和下一頁往後的文章列表組裝起來回傳&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task&amp;lt;IEnumerable&amp;lt;Post&amp;gt;&amp;gt; GetPosts(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	IBrowsingContext browser, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; baseUrl, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; pageUrl,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; remainingPages)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 組裝 Url 並設定 Cookie&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Url(baseUrl + pageUrl);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	browser.SetCookie(url, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;over18=1&amp;#39;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; document = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; browser.OpenAsync(url);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 取出所有文章標題&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; postSource = document.QuerySelectorAll(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.r-ent&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; posts = postSource.Select(post =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; titleElement = post.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.title &amp;gt; a&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = titleElement?.InnerHtml;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; link = titleElement?.GetAttribute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;href&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pushString = post.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.nrec &amp;gt; span&amp;#34;&lt;/span&gt;)?.InnerHtml;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pushCount =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			pushString == &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;爆&amp;#34;&lt;/span&gt; ? &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			Int16.TryParse(pushString, &lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; push) ? push : &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Post
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			Title = title,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			Link = link,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			Push = pushCount
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	.Where(post =&amp;gt; post.Title != &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 取得下一頁的連結&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; nextPageUrl = document
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		.QuerySelector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div.btn-group.btn-group-paging &amp;gt; a:nth-child(2)&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		.GetAttribute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;href&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	document.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 檢查剩餘頁數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	remainingPages--;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (remainingPages == &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; posts;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 組裝遞迴取得的文章列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; nextPagePosts = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; GetPosts(browser, baseUrl, nextPageUrl, remainingPages);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; posts.Concat(nextPagePosts);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;雖然也可以把詳細的步驟再拆得更細，像是把組裝 &lt;code&gt;Post&lt;/code&gt; 的部分拆出去私有方法，或是多加一層迴圈進到文章內抓取照片等等，不過現在我們只需要穩定拿到文章資訊就行&lt;/p&gt;
&lt;p&gt;接著再稍微修改一下，外面呼叫方法的部份只需要負責建立 Broswer 和定下第一頁的 Url 就好了&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; Task Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = AngleSharp.Configuration.Default
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		.WithDefaultLoader()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		.WithDefaultCookies();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; browser = BrowsingContext.New(config);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; baseUrl = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://www.ptt.cc&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; indexUrl = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/bbs/Beauty/index.html&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; pages = &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; posts = &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; GetPosts(browser, baseUrl, indexUrl, pages);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就大功告成，一次抓它個十頁都沒有問題囉！&lt;/p&gt;
&lt;p&gt;最後就可以按照我們的要求來處理 &lt;code&gt;posts&lt;/code&gt; 的文章啦，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;posts.Where(post =&amp;gt; post.Push &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt;).Dump();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;馬上就可以抓出十頁內 90 推以上的文，所以說 Linq 就是方便哪。&lt;/p&gt;
&lt;p&gt;到這邊我們已經可以很靈活地去運用這個列表了，像是把撈出來的文章連結搭配 &lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;LineNotify&lt;/a&gt; 做個推播通知啦，還是乾脆掛到排程服務去定時爬資料啦，都是很彈性很自由的了。&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;最後再一次整理本篇的操作流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;從 &lt;code&gt;AngleSharp.Configuration.Default&lt;/code&gt; 建立組態&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;BrowsingContext.New(config)&lt;/code&gt; 來建立 Browser&lt;/li&gt;
&lt;li&gt;根據需求調整組態和 Browser，例如 &lt;code&gt;SetCookie&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;browser.OpenAsync(url)&lt;/code&gt; 把網頁內容抓回來&lt;/li&gt;
&lt;li&gt;使用 篩選器 搭配 &lt;code&gt;QuerySelector&lt;/code&gt; 等方法，從網頁內容中抓出我們要的資訊&lt;/li&gt;
&lt;li&gt;網頁爬完之後可以順手 &lt;code&gt;Close()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;自由發揮&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;步驟其實非常簡單，各位朋友可以出發去動手抓自己想要的網頁囉！&lt;/p&gt;
&lt;p&gt;例如說&lt;a href=&#34;https://gist.github.com/Igouist/039148b1aaaa8e3073f5e135ff689f9b&#34;&gt;抓一下股票資訊&lt;/a&gt;啦、&lt;a href=&#34;https://gist.github.com/Igouist/ebfc29be9e350bb7c289f05df694535b&#34;&gt;稽查朋友在 PTT 的留言&lt;/a&gt;啦，都蠻有趣（？）的呢&lt;/p&gt;
&lt;p&gt;總之，當你需要在 C# 能簡單使用的小爬蟲，就是 AngleSharp 出場的時候啦！&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/kinanson/2017/08/30/085049&#34;&gt;使用類似 javascript selector 來爬網站的工具 - AngleSharp | kinanson的技術回憶&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dannyliu.me/%E7%94%A8-net-core%E5%81%9A%E7%B6%B2%E9%A0%81%E7%88%AC%E8%9F%B2%E6%8A%93%E5%8F%96%E8%B3%87%E6%96%99-%E4%BD%BF%E7%94%A8httpclicent%E8%88%87anglesharp/&#34;&gt;用 .NET Core 做網頁爬蟲抓取資料 - 使用 HttpClicent 與 AngleSharp - 長庚的作業簿&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://anglesharp.github.io/docs/01-articles&#34;&gt;AngleSharp - Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.runoob.com/cssref/css-selectors.html&#34;&gt;CSS 選擇器 | 菜鳥教程 (runoob.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/59219106/parsing-css-with-anglesharp&#34;&gt;c# - Parsing CSS with AngleSharp - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>《艾爾登法環》白金心得</title>
      <link>https://igouist.github.io/post/2022/06/elder-ring-clear/</link>
      <pubDate>Fri, 03 Jun 2022 23:25:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/06/elder-ring-clear/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/YTNa8sz.webp&#34;
  alt=&#34;Image&#34;width=&#34;1088&#34; height=&#34;501&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是一篇遲來的白金炫耀廢文。&lt;/p&gt;
&lt;p&gt;這次的成就對蒐集東西的要求比較少，沒有像黑魂一樣需要在同個場景來回打怪刷道具。通常只需要經歷幾個主要的劇情，然後到各地探險、打倒一些Ｂｏｓｓ，白金獎盃就幾乎到手了。&lt;/p&gt;
&lt;p&gt;對我這種一半時間都在到處逛街、動不動迷路然後就被王打死的人來說，實在是福音。畢竟我真的很討厭重複打同隻怪刷東西，去年的黑魂也卡在刷誓約物品很久很久。讚美法環，法環拯救了我的靈魂。&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;Ｂｏｓｓ戰給玩家的攻略手段也變得很多&lt;/strong&gt;。除了可以正面舉起武器硬肛以外，也可以搭配一些強勢或對Ｂｏｓｓ有利的技能，甚至可以召喚ＮＰＣ和其他玩家衝上去一陣圍毆。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/un6jVUm.webp&#34;
  alt=&#34;Image&#34;width=&#34;1919&#34; height=&#34;984&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我個人是覺得這樣的處理方式很棒，能同時滿足一般玩家、自我挑戰的苦行僧、不擇手段的外鄉人。&lt;/p&gt;
&lt;p&gt;更有趣的是可以看&lt;strong&gt;社群互嘴&lt;/strong&gt;，假老屁股嘴新玩家是草莓族之類的。畢竟我的樂趣一半以上都建立在跟朋友聊法環梗，還有看社群嘴砲及搞笑影片、各派法環歷史學家對遊戲劇情的分析（腦補）等等，社群論戰是絕不可少的&lt;/p&gt;
&lt;p&gt;另外不得不說的是：這遊戲雖然大多時候是個單機遊戲，但玩家間的互動相當活絡，除了遊戲裡可以召喚朋友一起打架，或是跟其他玩家打架以外，玩家也能在地版上留言給其他玩家，例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;懸崖前面留「前有寶箱」騙人跳崖&lt;/li&gt;
&lt;li&gt;牆壁前面寫「有隱藏道路」讓人撞牆&lt;/li&gt;
&lt;li&gt;在烏龜前面寫「很可能是狗」&lt;/li&gt;
&lt;li&gt;在龍前面寫「狗」（到底什麼不是狗？）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但雖然搞笑留言跟惡意玩家真的很多（我就跳崖了幾次），但也會看見很暖的留言，尤其是打王前通常都會有「加油！不要放棄」，被怪物襲擊的地方前也都會有其他玩家的警告。啊啊，讚美太陽。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/NIt1IZI.webp&#34;
  alt=&#34;Image&#34;width=&#34;1919&#34; height=&#34;984&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;和黑魂的另一個最大差別是&lt;strong&gt;開放世界&lt;/strong&gt;，這其實也大大降低了遊戲難度。畢竟以前卡關的時候就是只能一直死、一直死、死到關遊戲。&lt;/p&gt;
&lt;p&gt;現在可以先去打打別的地方，提升一下等級和武器再回來挑戰，甚至還有熱門練功點，整個推圖的壓力就小了不少，像我就逛街逛到沒什麼推主線進度，都在到處找地城倒斗搶東西，還有找風景和劇情過場拍照。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/E3hASGx.webp&#34;
  alt=&#34;Image&#34;width=&#34;1920&#34; height=&#34;1080&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;說到地城，我原本以為會像上古卷軸的洞窟那樣，絕大多數都是免洗寶箱探勘處，進去就是閉著眼睛一路殺。但想不到竟然還有&lt;strong&gt;利用重複場景和屍體來欺騙玩家的神奇地城&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那種「我怎麼又走回來了！？」的焦躁感真是冒險精隨，迷路半小時也不會氣憤，反而在搞懂之後會忍不住讚嘆。英高！你果然是最會搞玩家的男人！&lt;/p&gt;
&lt;p&gt;大概這樣。現正受到同事的蠱惑，又開了新角色挑戰限制等級＋只用長劍和盾牌通關，結果被碎星大哥壓在地上磨擦ＩＮＧ囧。 &lt;del&gt;太痛苦了，乾脆來寫廢文，所以才更了這篇。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/H9U4pyD.webp&#34;
  alt=&#34;Image&#34;width=&#34;1100&#34; height=&#34;592&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

富樫都開始畫獵人了，乾脆在血源上ＰＣ前來挑戰部落格週更吧？開玩笑的。&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (7): 使用 Fluent Validation 來驗證參數吧</title>
      <link>https://igouist.github.io/post/2022/03/newbie-7-fluent-validation/</link>
      <pubDate>Sun, 13 Mar 2022 20:03:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/03/newbie-7-fluent-validation/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/p6aSDH9.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#前言&#34;&gt;前言&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#專案現況&#34;&gt;專案現況&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝-fluent-validation&#34;&gt;安裝 Fluent Validation&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#撰寫-validator&#34;&gt;撰寫 Validator&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#使用內建的驗證規則&#34;&gt;使用內建的驗證規則&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-must-來自訂驗證規則&#34;&gt;使用 Must 來自訂驗證規則&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-when-來指定驗證條件適用的場景&#34;&gt;使用 When 來指定驗證條件適用的場景&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-withname-和-withmessage-來自訂驗證訊息&#34;&gt;使用 WithName 和 WithMessage 來自訂驗證訊息&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-setvalidator-來指定成員的驗證器&#34;&gt;使用 SetValidator 來指定成員的驗證器&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#指定-cascademodestop-來提早返回&#34;&gt;指定 CascadeMode.Stop 來提早返回&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#將前述的規則實作成-validator&#34;&gt;將前述的規則實作成 Validator&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-validator-進行驗證&#34;&gt;使用 Validator 進行驗證&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#註冊-validator-來自動進行驗證&#34;&gt;註冊 Validator 來自動進行驗證&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#同系列文章&#34;&gt;同系列文章&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#附錄fluentvalidation-內建驗證方法-小抄&#34;&gt;附錄：FluentValidation 內建驗證方法 小抄&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;p&gt;這是俺整理公司新訓內容的第七篇文章，目標是紀錄 Fluent Validation 這個好用套件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FluentValidation 可以幫我們將 Api 傳入的參數的檢查用更口語、更乾淨的方式去處理&lt;/strong&gt;，除了可以將檢查邏輯拆分成單獨的 Validator 類別，更提供了許多內建的檢查規則和自訂的彈性，相當方便。&lt;/p&gt;
&lt;p&gt;並且因為將參數的檢查邏輯整理出去，就可以和 Controller 本身的工作做簡單的拆分，達到關注點分離的目標。&lt;/p&gt;
&lt;p&gt;現在就讓我們來認識一下這個好用工具吧！首先要從很久很久以前開始說起…&lt;/p&gt;
&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;西元前的某一天，憂心的皇帝在朝堂內繞著柱子走，突然大臣奪門而入。&lt;/p&gt;
&lt;p&gt;大臣：「陛下！敵軍已經攻到國境內啦！」&lt;/p&gt;
&lt;p&gt;皇帝大驚：『邊境的那些檢查站和關口難道都陷落了嗎？不可能！』&lt;/p&gt;
&lt;p&gt;大臣：「陛下，有內奸和敵國勾結，檢查站完全沒檢查！髒資料已經闖進來了！」&lt;/p&gt;
&lt;p&gt;皇帝喊了一聲：『怎麼可能！讓朕看看！』就打開 Controller 和前一個版本的 Git Log，這一看差點就昏了過去。&lt;/p&gt;
&lt;p&gt;原來 Controller 的舊程式碼就已經很亂了，檢查參數的條件 if/else 和其他呼叫的方法、組裝資料都雜在一起。結果這次專案改動時，某一行就被內奸改壞了，關鍵的參數竟然沒檢查到！&lt;/p&gt;
&lt;p&gt;『可，可惡！來人啊，把工程師推出午門斬首！』&lt;/p&gt;
&lt;p&gt;「皇上！他已經離職啦！」&lt;/p&gt;
&lt;p&gt;皇帝跌坐在地，懊悔地說：『如果當初有好好把檢查參數跟實際組資料的部份都拆開的話，也許就不會這樣了…』&lt;/p&gt;
&lt;p&gt;「是啊，如果我們有用 Fluent Validation…！」&lt;/p&gt;
&lt;h2 id=&#34;專案現況&#34;&gt;專案現況&lt;/h2&gt;
&lt;p&gt;大臣提到的 &lt;a href=&#34;https://fluentvalidation.net/&#34;&gt;FluentValidation&lt;/a&gt; 是一套能幫我們把傳入參數的分離出去、用更口語化的方式去撰寫的工具。&lt;/p&gt;
&lt;p&gt;……如果當時他們有使用 Fluent Validation 來把驗證的邏輯和規則跟原本很亂的 Controller 切分的話，說不定就能及時發現問題吧，大概。&lt;/p&gt;
&lt;p&gt;為了不要步上他們的後塵，就讓我們直接回到本系列的卡牌管理 API 服務來加上這個好用工具吧！&lt;/p&gt;
&lt;p&gt;假設我們在新增一張新的卡牌時，會針對裡面的欄位做一連串檢查：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert([FromBody] CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊需要對參數做檢查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Attack &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的攻擊力不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Health &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的生命值不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Cost &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的使用成本不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Description != &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameter.Description.Length &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的敘述說明必須少於三十字&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(parameter.Name))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的名稱不可為空白&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Name.Length &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的名稱必須少於十五字&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 AutoMapper 把 Parameter Model 轉換成 Info Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardParameter, CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫依賴的 Service 層寫入資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isInsertSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Insert(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isInsertSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到這個新增卡片的方法中，真正操作的只有最後呼叫相關服務來寫入資料的部份，前面就是針對參數做一整串的 if 檢查。隨著傳入參數要檢查的東西變多，檢查的過程也會越來越大坨。&lt;/p&gt;
&lt;p&gt;這時候，&lt;strong&gt;只要有了 Fluent Validation，我們就可以在參數檢查上做得更好！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;安裝-fluent-validation&#34;&gt;安裝 Fluent Validation&lt;/h2&gt;
&lt;p&gt;因為我們的示範專案是 .net Core 的 Api，所以讓我們安裝 FluentValidation.AspNetCore&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5ubV1VG.webp&#34;
  alt=&#34;&#34;width=&#34;1559&#34; height=&#34;396&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：這包裡面包含了 Fluent Validation 本體和支援 Dotnet Core 的 DI（DependencyInjection）工具。如果習慣將驗證部分拆成其他類別庫，或是不需要 DI 的朋友可以嘗試安裝 Fluent Validation 就好。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;撰寫-validator&#34;&gt;撰寫 Validator&lt;/h2&gt;
&lt;p&gt;要使用 Fluent Validation 來驗證參數，&lt;strong&gt;首先我們必須建立一個針對該參數的驗證器（Validator），並繼承 &lt;code&gt;AbstractValidator&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; 的泛型選擇驗證對象的類別即可，接著就可以在 Validator 的建構式來註冊我們要的驗證邏輯。&lt;/p&gt;
&lt;p&gt;現在就讓我們針對前面例子的 &lt;code&gt;CardParameter&lt;/code&gt; 來建立 &lt;code&gt;CardParameterValidator&lt;/code&gt; 吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BWDZX6R.webp&#34;
  alt=&#34;&#34;width=&#34;249&#34; height=&#34;66&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// Card Parameter 的驗證器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardParameterValidator&lt;/span&gt; : AbstractValidator&amp;lt;CardParameter&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器的建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;使用內建的驗證規則&#34;&gt;使用內建的驗證規則&lt;/h3&gt;
&lt;p&gt;現在我們已經針對 &lt;code&gt;CardParameter&lt;/code&gt; 建立了驗證器，接著讓我們處理驗證邏輯的部分吧。&lt;/p&gt;
&lt;p&gt;當我們要驗證某個欄位的時候，就需要使用 &lt;code&gt;RuleFor&lt;/code&gt; 來告訴驗證器現在驗證的欄位，後面再利用 Fluent Validation 提供的各種驗證語法來進行驗證。&lt;/p&gt;
&lt;p&gt;例如我們前面的「卡片的攻擊力不應為負數」，也就是 Attack 必須大於等於０，這邊就可以使用 &lt;code&gt;GreaterThanOrEqualTo&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果驗證的對象是個串列之類的，也支援用 &lt;code&gt;RuleForEach&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;例如我們的卡片可以有多個別名（&lt;code&gt;List&amp;lt;string&amp;gt; Alias&lt;/code&gt; 之類的），且裡面每個別名都不可以是空的，就可以：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleForEach(card =&amp;gt; card.Alias)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .NotEmpty(); &lt;span style=&#34;color:#75715e&#34;&gt;// 不可為空&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;大部份的狀況下，使用內建的驗證語法就很夠用了。可以參照官方文檔的 &lt;a href=&#34;https://docs.fluentvalidation.net/en/latest/built-in-validators.html#built-in-validators&#34;&gt;Built-in Validators&lt;/a&gt;，裡面每一項都有範例和參數說明。&lt;/p&gt;
&lt;p&gt;平常比較會遇到的就是 &lt;code&gt;NotNull&lt;/code&gt;、&lt;code&gt;NotEmpty&lt;/code&gt; 和字串長度檢查或是數值大小的。如果是ㄧ些表單需要驗證的話，就還會用到 &lt;code&gt;EmailAddress&lt;/code&gt; 等等。&lt;/p&gt;
&lt;p&gt;那俺身為一個 &lt;del&gt;懶惰&lt;/del&gt; 節能減碳工程師，當然有在 Linqpad 中準備一份範例 &lt;del&gt;才能隨時抄嘛&lt;/del&gt;，這邊也會附在文末的&lt;a href=&#34;#%E9%99%84%E9%8C%84fluentValidation-%E5%85%A7%E5%BB%BA%E9%A9%97%E8%AD%89%E6%96%B9%E6%B3%95-%E5%B0%8F%E6%8A%84&#34;&gt;附錄&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;使用-must-來自訂驗證規則&#34;&gt;使用 Must 來自訂驗證規則&lt;/h3&gt;
&lt;p&gt;當然，我們也會遇到內建的驗證規則不夠用的情況。這時候就可以使用 &lt;code&gt;Must()&lt;/code&gt; 來傳入自訂的規則，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 使用 Must 來自訂規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .Must(attack =&amp;gt; attack &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &amp;amp;&amp;amp; attack &amp;lt;= &lt;span style=&#34;color:#ae81ff&#34;&gt;3000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;只要在 &lt;code&gt;Must&lt;/code&gt; 裡面指定要驗證的規則就可以囉！&lt;/p&gt;
&lt;h3 id=&#34;使用-when-來指定驗證條件適用的場景&#34;&gt;使用 When 來指定驗證條件適用的場景&lt;/h3&gt;
&lt;p&gt;除了規則可以彈性處理以外，有時候我們也會遇到「有某個條件成立才驗證指定欄位」的情況&lt;/p&gt;
&lt;p&gt;假設我們的卡牌又分成「怪獸卡」和「魔法卡」等等，而卡牌本身又有個 int? 的攻擊力欄位&lt;/p&gt;
&lt;p&gt;規則又要求：「怪獸卡必須是具有攻擊力的」&lt;/p&gt;
&lt;p&gt;雖然直覺上就會想要用 &lt;code&gt;if (卡牌是怪獸卡)&lt;/code&gt; 之類的方式去另外做，但就會變得有點兒醜&lt;/p&gt;
&lt;p&gt;這時候我們就能用 &lt;code&gt;When&lt;/code&gt; 的方式來指定驗證條件的前提：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 目標：當 卡牌 是 怪獸卡 的時候，攻擊力不可為 Null &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 針對指定規則加上適用場景&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .NotNull()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .When(card =&amp;gt; card.CardType &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; CardType.Monster);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 針對指定場景加上適用規則，我個人比較喜歡這種&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.When(card =&amp;gt; card.CardType &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; CardType.Monster, () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack).NotNull();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 以上兩種寫法是相同的，但我個人比較喜歡先 When 才指定規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 除了比較符合日常口語以外，也能把同樣場景的規則整理在一起&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就像 if 有 else，這邊的 When 也有 Otherwise 來幫忙處理剩下的狀況&lt;/p&gt;
&lt;p&gt;假設我們除了怪獸卡以外的卡片，例如魔法卡之類的，都不應該有攻擊力，就可以這樣寫：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 目標：當 卡牌 是 怪獸卡 的時候，攻擊力不可為 Null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 目標：當 卡牌 不是 怪獸卡 的時候，攻擊力必須為 Null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.When(card =&amp;gt; card.CardType &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; CardType.Monster, () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack).NotNull();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .Otherwise(() =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack).Null();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;使用-withname-和-withmessage-來自訂驗證訊息&#34;&gt;使用 WithName 和 WithMessage 來自訂驗證訊息&lt;/h3&gt;
&lt;p&gt;雖然內建的驗證規則都有提供制式的回傳訊息，例如對 Attack 做 &lt;code&gt;.GreaterThanOrEqualTo(0)&lt;/code&gt; 驗證失敗時，會得到「&amp;lsquo;Attack&amp;rsquo; 必須大於或等於 &amp;lsquo;0&amp;rsquo;」的訊息&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KIEDyhI.webp&#34;
  alt=&#34;Image&#34;width=&#34;797&#34; height=&#34;365&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;但我們也可以使用 &lt;code&gt;WithMessage&lt;/code&gt; 來針對驗證規則指定失敗時的自訂訊息：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .WithMessage(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的攻擊力不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣在驗證完的 ValidationResult 裡，就會變成我們指定了錯誤訊息了。&lt;/p&gt;
&lt;p&gt;那如果我們想用內建的訊息，但又希望「Attack」這個欄位名稱不要顯示出來，而是顯示我們要的「攻擊力」這個名稱呢？這時候就可以使用 &lt;code&gt;WithName()&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .WithName(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;攻擊力&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣原本的「&amp;lsquo;Attack&amp;rsquo; 必須大於或等於 &amp;lsquo;0&amp;rsquo;」，就會變成「&amp;lsquo;攻擊力&amp;rsquo; 必須大於或等於 &amp;lsquo;0&amp;rsquo;」囉！&lt;/p&gt;
&lt;p&gt;當然，要把兩個結合起來用也是可以的，只要在字串加上 &lt;code&gt;{PropertyName}&lt;/code&gt; 讓他去讀欄位名稱就好囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .WithName(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;攻擊力&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .WithMessage(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的{PropertyName}不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就能拿到「卡片的攻擊力不可為負數」囉！&lt;/p&gt;
&lt;h3 id=&#34;使用-setvalidator-來指定成員的驗證器&#34;&gt;使用 SetValidator 來指定成員的驗證器&lt;/h3&gt;
&lt;p&gt;我們前面說了許多針對欄位驗證的工具，但平常我們的類別內的成員有可能會是另一個類別。這時候我們就可以用 &lt;code&gt;SetValidator&lt;/code&gt; 來指定該成員的驗證器。&lt;/p&gt;
&lt;p&gt;假設說我們的卡片怪獸現在能夠穿戴裝備了，同時我們也有裝備的 Validator：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardType Type { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Equipment Equipment { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;;} &lt;span style=&#34;color:#75715e&#34;&gt;// 可以穿裝備了！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Equipment&lt;/span&gt; { }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EquipmentValidator&lt;/span&gt; : AbstractValidator&amp;lt;Card&amp;gt; { }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這時候我們在寫規則的時候就可以：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Equipment)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .SetValidator(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; EquipmentValidator());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;指定-cascademodestop-來提早返回&#34;&gt;指定 CascadeMode.Stop 來提早返回&lt;/h3&gt;
&lt;p&gt;很多時候，我們並不需要全部的規則都驗證完才返回，而是只要檢查清單中的一項不符合，那就直接掰掰。這時我們就可以更改驗證器的 &lt;code&gt;CascadeMode&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 驗證失敗時即停止&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.CascadeMode = FluentValidation.CascadeMode.Stop;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CascadeMode 原先預設會是 &lt;code&gt;Continue&lt;/code&gt;，也就是即使驗證失敗也會繼續執行&lt;/p&gt;
&lt;p&gt;例如說它可能一口氣犯了好幾條，就會全部驗證完再一併列出所有驗證失敗的項目：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ngLOMaE.webp&#34;
  alt=&#34;Image&#34;width=&#34;422&#34; height=&#34;159&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當我們把驗證器的 CascadeMode 指定為 &lt;code&gt;Stop&lt;/code&gt; 之後，犯第一條就會直接原地遣返：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/deSUlPf.webp&#34;
  alt=&#34;Image&#34;width=&#34;191&#34; height=&#34;61&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了指定整個驗證器以外，我們也可以單獨指定某一條規則為天條：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .Cascade(CascadeMode.Stop)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一來只要觸犯這條就會直接送客，皆大歡喜。&lt;/p&gt;
&lt;h3 id=&#34;將前述的規則實作成-validator&#34;&gt;將前述的規則實作成 Validator&lt;/h3&gt;
&lt;p&gt;前面我們介紹了如何撰寫一個 Validator，是時候讓我們來處理文章最一開始的範例了！&lt;/p&gt;
&lt;p&gt;這邊附一下文章開頭的範例，也就是目前的卡牌系統 Controller 裡的新增卡片方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert([FromBody] CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 一堆檢查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Attack &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的攻擊力不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Health &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的生命值不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Cost &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的使用成本不可為負數&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Description != &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameter.Description.Length &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的敘述說明必須少於三十字&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(parameter.Name))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的名稱不可為空白&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (parameter.Name.Length &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;卡片的名稱必須少於十五字&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 AutoMapper 把 Parameter Model 轉換成 Info Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardParameter, CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫依賴的 Service 層寫入資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isInsertSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Insert(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isInsertSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到在範例中，我們針對一張新的卡牌，需要檢查的項目有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻擊力不可為負數&lt;/li&gt;
&lt;li&gt;生命值不可為負數&lt;/li&gt;
&lt;li&gt;使用成本不可為負數&lt;/li&gt;
&lt;li&gt;敘述說明必須少於三十字&lt;/li&gt;
&lt;li&gt;名稱不可以為空值&lt;/li&gt;
&lt;li&gt;名稱必須少於十五字&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;現在讓我們建立 CardParameter 的 Validator，並用 RuleFor 加上這些規則吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// Card Parameter 的驗證器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardParameterValidator&lt;/span&gt; : AbstractValidator&amp;lt;CardParameter&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 驗證器的建構式: 在這裡註冊我們要驗證的規則&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardParameterValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Health)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Cost)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Description)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .NotNull()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .MaximumLength(&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .NotEmpty()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .MaximumLength(&lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以感覺到比起整串 if/else，這邊整理得更加簡短、也更加口語了。&lt;/p&gt;
&lt;h2 id=&#34;使用-validator-進行驗證&#34;&gt;使用 Validator 進行驗證&lt;/h2&gt;
&lt;p&gt;現在我們已經準備好了 Validator 了，讓我們回到原本的 Controller 來使用它吧！&lt;/p&gt;
&lt;p&gt;首先讓我們把原本的 if/else 部分移除：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊需要對參數做檢查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 AutoMapper 轉換 Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardParameter,CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫 Service 層寫入資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isInsertSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Insert(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isInsertSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們直接建立一個驗證器出來使用，並且用 &lt;code&gt;Validate&lt;/code&gt; 來驗證參數：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; validator = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardParameterValidator();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; validationResult = validator.Validate(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;加上驗證器的樣子是像這樣的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊需要對參數做檢查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; validator = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardParameterValidator();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; validationResult = validator.Validate(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 AutoMapper 把 Parameter Model 轉換成 Info Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardParameter, CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫依賴的 Service 層寫入資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isInsertSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Insert(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isInsertSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們就可以使用 &lt;code&gt;Validate&lt;/code&gt; 回傳的 &lt;code&gt;ValidationResult&lt;/code&gt; 來看驗證結果。&lt;/p&gt;
&lt;p&gt;先讓我們用 Linqpad 的小範例把 &lt;code&gt;ValidationResult&lt;/code&gt; 的內容印出來看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UcBHhci.webp&#34;
  alt=&#34;Image&#34;width=&#34;1327&#34; height=&#34;481&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到，&lt;code&gt;IsValid&lt;/code&gt; 會告訴我們是不是有通過驗證。如果沒有通過驗證的話，&lt;code&gt;Errors&lt;/code&gt; 就會有驗證失敗的內容。&lt;/p&gt;
&lt;p&gt;現在讓我們加上驗證結果的檢查吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 這邊需要對參數做檢查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; validator = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardParameterValidator();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; validationResult = validator.Validate(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 如果沒有通過檢查，就把訊息串一串丟回去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (validationResult.IsValid &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; errorMessages = validationResult.Errors.Select(e =&amp;gt; e.ErrorMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; resultMessage = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Join(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;,&amp;#34;&lt;/span&gt;, errorMessages);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; BadRequest(resultMessage); &lt;span style=&#34;color:#75715e&#34;&gt;// 直接回傳 400 + 錯誤訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 AutoMapper 把 Parameter Model 轉換成 Info Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardParameter, CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫依賴的 Service 層寫入資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isInsertSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Insert(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isInsertSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在讓我們來呼叫 API 試試吧！&lt;/p&gt;
&lt;p&gt;這邊直接使用先前建置好的 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger/&#34;&gt;Swagger&lt;/a&gt; 頁面來測試，並且故意把攻擊力打成負數：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/dL34bdN.webp&#34;
  alt=&#34;Image&#34;width=&#34;266&#34; height=&#34;191&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/omN0dax.webp&#34;
  alt=&#34;Image&#34;width=&#34;290&#34; height=&#34;197&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到回傳的確變成了我們驗證失敗的訊息。&lt;/p&gt;
&lt;h2 id=&#34;註冊-validator-來自動進行驗證&#34;&gt;註冊 Validator 來自動進行驗證&lt;/h2&gt;
&lt;p&gt;不過都已經到了 .net Core 時代，&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/&#34;&gt;依賴注入&lt;/a&gt; 已經是內建的功能下，還要用 &lt;code&gt;new&lt;/code&gt; 一個驗證器這種直接依賴的方式還是有點不太舒服……所以 Fluent Validation 也有提供自動驗證的作法！&lt;/p&gt;
&lt;p&gt;首先讓我們到熟悉的 &lt;code&gt;Startup.cs&lt;/code&gt; → &lt;code&gt;ConfigureServices&lt;/code&gt; 進行註冊：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddFluentValidation();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddTransient&amp;lt;IValidator&amp;lt;CardParameter&amp;gt;, CardParameterValidator&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;補充：如果不想明確註冊每個類別的 Validator，也可以直接在 &lt;code&gt;AddFluentValidation&lt;/code&gt; 的時候，使用反射組件自動註冊的方式來抓該組件底下所有的 Validator，比較不怕出錯、也更方便：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddFluentValidation(fv =&amp;gt; fv.RegisterValidatorsFromAssemblyContaining&amp;lt;Startup&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;註冊好了之後就讓我們回到 Controller，並大膽地把驗證器相關的部分刪掉吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 將原本的參數檢查刪掉了！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 用 AutoMapper 把 Parameter Model 轉換成 Info Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardParameter, CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫依賴的 Service 層寫入資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isInsertSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Insert(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isInsertSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後讓我們用 Swagger 再試一次看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/YLRkBg0.webp&#34;
  alt=&#34;Image&#34;width=&#34;522&#34; height=&#34;300&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到 Fluent Validation 自動幫我們擋了下來！&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;當檢查參數的過程越來越冗長，為了做到關注點分離、讓方法本體更專注在流程上的處理，我們會選擇將檢查參數的邏輯拆分出去，例如拆成一個私有的 Function 等等。&lt;/p&gt;
&lt;p&gt;這時候 Fluent Validation 就提供了我們一個更棒、更優雅的選擇。&lt;/p&gt;
&lt;p&gt;本篇稍微記錄了 Fluent Validation 的基本用法，足夠應付大多數的使用場景。簡單小結如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;繼承 &lt;code&gt;AbstractValidator&amp;lt;T&amp;gt;&lt;/code&gt; 來實作我們的驗證器
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;RuleFor&lt;/code&gt; 來針對參數的欄位撰寫規則&lt;/li&gt;
&lt;li&gt;有許多內建的規則可以使用；或是使用 &lt;code&gt;Must&lt;/code&gt; 來自定規則&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;When&lt;/code&gt; 可以指定規則生效的前提&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;WithName&lt;/code&gt; 可以指定欄位在訊息顯示的名稱&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;WithMessage&lt;/code&gt; 可以自訂驗證失敗時的訊息&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;SetValidator&lt;/code&gt; 可以指定參數某個成員要用的驗證器&lt;/li&gt;
&lt;li&gt;加上 &lt;code&gt;CascadeMode.Stop&lt;/code&gt; 就可以在驗證失敗時直接跳出&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用 Validator 進行驗證
&lt;ul&gt;
&lt;li&gt;可以直接建立驗證器來驗證
&lt;ul&gt;
&lt;li&gt;如：&lt;code&gt;new CardParameterValidator().Validate(parameter);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;也可以註冊進行自動驗證
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;Startup&lt;/code&gt; 的 &lt;code&gt;ConfigureServices&lt;/code&gt; 加上 &lt;code&gt;AddFluentValidation&lt;/code&gt; 及驗證器的註冊&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;當然，FluentValidation 還有許多進階的應用可以探索，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://docs.fluentvalidation.net/en/latest/testing.html&#34;&gt;FluentValidation.TestHelper&lt;/a&gt; 來替驗證器寫單元測試&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://docs.fluentvalidation.net/en/latest/rulesets.html&#34;&gt;RuleSets&lt;/a&gt; 來將驗證器規則分成多個規則集，再針對狀況使用
&lt;ul&gt;
&lt;li&gt;例如新增和更新的功能共用同個參數的時候，就可以考慮使用規則集來指定各自要驗證哪些規則&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;需要客製化驗證失敗時回傳的 ViewModel 時，可以將 &lt;code&gt;validator.Validate&lt;/code&gt; 包裝到 Attribute 裡進行攔截及驗證
&lt;ul&gt;
&lt;li&gt;實際案例，敝司對 API 回傳格式有嚴格規範，於是前輩就在 Attribute 裡實例化 Validator 再從 actionContext 抓出參數驗證…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;諸如此類，畢竟在參數驗證的路上發生什麼事也不奇怪，請再根據狀況自由地調整吧。&lt;/p&gt;
&lt;p&gt;那麼，我們下回見～&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/04/30/005529&#34;&gt;料理佳餚 - 讓 Fluent Validation 把參數的檢查條件口語化 | 軟體主廚的程式料理廚房 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://progressbar.tw/posts/117&#34;&gt;C# .Net MVC 06. 驗證參數- 透過FluentValidation (progressbar.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://eugenesu0515.github.io/2021/08/24/fluent-validation/&#34;&gt;DotnetCore 後端驗證神器:Fluent Validation | Eugene&amp;rsquo;s Blog (eugenesu0515.github.io)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.fluentvalidation.net/en/latest/aspnet.html&#34;&gt;ASP.NET Core — Fluent Validation documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/shadowkk/2019/07/23/140127&#34;&gt;Fluent Validation 使用ActionFilter來驗證參數 | 菜鳥工程師訓練營 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.xcode.me/post/5849&#34;&gt;基于 .NET 的 Fluent Validation 验证教程-零度 (xcode.me)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.fluentvalidation.net/en/latest/built-in-validators.html&#34;&gt;FluentValidation documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;附錄fluentvalidation-內建驗證方法-小抄&#34;&gt;附錄：FluentValidation 內建驗證方法 小抄&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sut = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Cost = &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Blue-Eyes White Dragon&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Type = CardType.Monster
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; validator = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardValidator();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = validator.Validate(sut);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardType Type { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; CardType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Monster = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardValidator&lt;/span&gt; : AbstractValidator&amp;lt;Card&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardValidator()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Fluent Validation 的 驗證器請參照&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// https://docs.fluentvalidation.net/en/latest/built-in-validators.html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// 驗證失敗時即停止&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;//this.CascadeMode = FluentValidation.CascadeMode.Stop;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 為了示範所以做成變數，平時可以直接 RuleFor().XXX() 串接驗證器即可&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; name = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; cost = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Cost);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; type = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(card =&amp;gt; card.Type);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 不可為 Null    &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.NotNull();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 必須為 Null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//name.Null();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 不可為空&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.NotEmpty();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 必須為空&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//name.Empty();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 不可相同&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.NotEqual(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Test Card&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 不可相同：也支持 StringComparer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.NotEqual(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Test Card&amp;#34;&lt;/span&gt;, StringComparer.OrdinalIgnoreCase);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 不可相同：也可以比較其他欄位（大多驗證器都支援）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.NotEqual(card =&amp;gt; card.Type.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 必須相同，其餘用法可參考 NotEqual&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.Equal(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Blue-Eyes White Dragon&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 長度限制，限定１～２００&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.Length(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 最大長度限制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.MaximumLength(&lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 最小長度限制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.MinimumLength(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 數值需低於目標值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.LessThan(&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 數值需低於或等於目標值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.LessThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 數值需高於目標值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.GreaterThan(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 數值需高於或等於目標值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.GreaterThanOrEqualTo(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 數值需介於兩個目標值之間&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.ExclusiveBetween(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 數值需介於兩個目標值之間（包含目標值）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.InclusiveBetween(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 檢查是否具有指定的位數，例如 (1, 4) = 小數點限１位、總位數限４位&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.RuleFor(x =&amp;gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;decimal&lt;/span&gt;)x.Cost).ScalePrecision(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 正則表達式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name.Matches(&lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;^[a-zA-Z-&amp;#39; ]*$&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 必須為信箱格式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//name.EmailAddress();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 必須為信用卡格式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//name.CreditCard();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 必須包含在列舉中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        type.IsInEnum();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 必須包含在列舉名稱中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//name.IsEnumName(typeof(CardType));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 指定驗證場景&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.GreaterThan(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;).When(card =&amp;gt; card.Type &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; CardType.Monster);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 指定驗證場景&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.When(card =&amp;gt; card.Type &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; CardType.Monster, () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            cost.GreaterThan(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// .Otherwise(() =&amp;gt; { cost.GreaterThan(0); });&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 最終大絕招：自訂驗證器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cost.Must(power =&amp;gt; power &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &amp;amp;&amp;amp; power &amp;lt;= &lt;span style=&#34;color:#ae81ff&#34;&gt;3000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Omni —— 實用的 Chrome 分頁書籤搜尋欄</title>
      <link>https://igouist.github.io/post/2022/03/omni/</link>
      <pubDate>Sun, 13 Mar 2022 20:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2022/03/omni/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/D29Htug.webp&#34;
  alt=&#34;Image&#34;width=&#34;712&#34; height=&#34;360&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://chrome.google.com/webstore/detail/omni-bookmark-history-tab/mapjgeachilmcbbokkgcbgpbakaaeehi&#34;&gt;Omni&lt;/a&gt; 是一款 Chrome 的擴充功能。它能夠讓你用快捷鍵叫出搜尋框，並直接&lt;strong&gt;搜尋當前開啟的分頁、書籤、歷史紀錄&lt;/strong&gt;等等。&lt;/p&gt;
&lt;p&gt;這個工具適合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;習慣分頁開很多的人，尤其是像我這種能分頁分組摺疊之後就開更多&lt;/li&gt;
&lt;li&gt;懶得用滑鼠去找分頁、也懶得 Ctrl Tab 逐個分頁切換的人&lt;/li&gt;
&lt;li&gt;書籤存了一大堆但每次都忘記放在哪裡，最後還是重新搜尋一次的人&lt;/li&gt;
&lt;li&gt;想在瀏覽器有方便的搜尋框（就像 Mac 的 Alfred 或 Windows 的 Powertoy 那樣）&lt;/li&gt;
&lt;li&gt;即使只有三個分頁，還是要在朋友面前打字裝潮的人&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;首先讓我們到&lt;a href=&#34;https://chrome.google.com/webstore/detail/omni-bookmark-history-tab/mapjgeachilmcbbokkgcbgpbakaaeehi&#34;&gt;擴充功能商店&lt;/a&gt;把 Omni 安裝進來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4Q9b4Au.webp&#34;
  alt=&#34;Image&#34;width=&#34;1040&#34; height=&#34;242&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝完畢重開一下瀏覽器就可以開始使用囉！&lt;/p&gt;
&lt;p&gt;接著只要按下快捷鍵（預設為 &lt;code&gt;Ctrl + Shift + K&lt;/code&gt;）就可以叫出搜尋視窗：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PFwSJRW.webp&#34;
  alt=&#34;Image&#34;width=&#34;710&#34; height=&#34;518&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果覺得預設的快捷鍵太卡，也可以設定擴充功能的快捷鍵。&lt;/p&gt;
&lt;p&gt;像我個人就習慣設定成和 Visual Studio 的跳轉 Ctrl+T 有關的按法（例如 Ctrl Shift T）&lt;/p&gt;
&lt;p&gt;這邊提供 Chrome 和 Edge 快捷鍵更改方式，如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chrome: 右上角擴充功能按鈕 → 管理擴充功能 → 左上工具列 → 鍵盤快捷鍵&lt;/li&gt;
&lt;li&gt;Edge: 右上角延伸模組按鈕 → 管理延伸模組 → 左側工具列 → 鍵盤快捷鍵&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;叫出搜尋欄時，除了當前開著的分頁以外，也會顯示一些 Chrome 內建常用的快捷鍵。&lt;/p&gt;
&lt;p&gt;除此之外，Omni 也有提供 Google 日曆的新增、Notion 的新增筆記等等頁面快捷鍵（不過這部分的場景蠻特定的，應該沒有搜尋分頁等功能常用）&lt;/p&gt;
&lt;p&gt;而在搜尋窗輸入文字後，就可以在分頁、書籤搜尋有該關鍵字的頁面。也可以使用關鍵字來指定搜尋範圍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/tabs&lt;/code&gt; 搜尋分頁&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/bookmarks&lt;/code&gt; 搜尋書籤&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/history&lt;/code&gt; 搜尋歷史紀錄&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整個擴充功能的使用就只有 &lt;strong&gt;「打開搜尋窗 → 輸入關鍵字 → 找到分頁 or 書籤」&lt;/strong&gt; 這麼簡單&lt;/p&gt;
&lt;p&gt;但在一些開了三四十個分頁的場合，或是像我一樣會將公司各個站台存放到書籤／我的最愛時，簡單的搜尋就能有不錯的效果。在這邊推薦給有同樣習慣的朋友們。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/alyssaxuu/omni&#34;&gt;alyssaxuu/omni (github.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://juejin.cn/post/7064912262095437861&#34;&gt;最近 Github 上爆火的 Chrome 生产力神器 Omni 是什么鬼？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>AutoMapper 使用 ConvertUsing 自定義類型轉換，將包含串列成員的物件映射為一組串列</title>
      <link>https://igouist.github.io/post/2021/12/automapper-convert-using/</link>
      <pubDate>Sun, 05 Dec 2021 11:20:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/12/automapper-convert-using/</guid>
      <description>&lt;p&gt;從朋友那兒聽到了用 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper&#34;&gt;AutoMapper&lt;/a&gt; 把串列成員物件攤平成一組串列的問題，發現了 &lt;strong&gt;ConvertUsing&lt;/strong&gt; 的好用，這邊就紀錄一下。&lt;/p&gt;
&lt;p&gt;事情是這樣的，首先有一個 &lt;code&gt;Parent&lt;/code&gt; 類別，其中包含著兩個成員：&lt;code&gt;Id&lt;/code&gt; 和串列的 &lt;code&gt;Child&lt;/code&gt; 類別，而 &lt;code&gt;Child&lt;/code&gt; 類別則只有一個成員 &lt;code&gt;Val&lt;/code&gt;，如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Parent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Child&amp;gt; Children { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Child&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;另外還有一個 &lt;code&gt;Target&lt;/code&gt; 類別，包含 &lt;code&gt;Id&lt;/code&gt; 和 &lt;code&gt;Val&lt;/code&gt; 兩個成員：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在的目標是：&lt;strong&gt;將一個有著 Child 串列的 Parent 映射成 Target 串列&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是說，假設我們的來源是這樣子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; boo = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Parent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Id = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Children = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Child&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Child { Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Child { Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;希望可以變成這樣子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; expect = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Target&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Target { Id = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Target { Id = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我之前遇到的時候，會直覺地將 Child 直接 Map 到 Target，再對 Target 做個 Foreach 來補上 Parent 的 Id。&lt;/p&gt;
&lt;p&gt;這次和朋友討論時，提到了另一個角度：雖然這樣的做法相當直覺快速，但其實並不能保證後續維護的人使用這組 Mappings 時，都知道這裡要補資料；況且此處的對應關係的確是 &lt;code&gt;Parent&lt;/code&gt; 到 &lt;code&gt;List&amp;lt;Target&amp;gt;&lt;/code&gt;，並非 &lt;code&gt;Child&lt;/code&gt; 到 &lt;code&gt;Target&lt;/code&gt; 而已，直覺上就怪怪的。若要解決這個問題，可能就要再包裝一層，把 Mapper 隔離出去做個轉換器之類的。&lt;/p&gt;
&lt;p&gt;但想想又覺得 AutoMapper 不可能沒提供這個場景能使用的方法才對，最後餵狗發現 &lt;strong&gt;AutoMapper 確實有提供 &lt;code&gt;ConvertUsing&lt;/code&gt; 來讓我們客製化轉換過程&lt;/strong&gt;，這邊就紀錄一下。&lt;/p&gt;
&lt;p&gt;在我們註冊映射關係的時候，可以用 &lt;code&gt;ConvertUsing&lt;/code&gt; 來直接定義轉換的過程。以這個例子來說，就可以：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .CreateMap&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .ConvertUsing(parent =&amp;gt; parent.Children.Select(child =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Id = parent.Id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Val = child.Val
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;像這樣告訴 AutoMapper：「從 &lt;code&gt;Parent&lt;/code&gt; 轉換到 &lt;code&gt;IEnumerable&amp;lt;Target&amp;gt;&lt;/code&gt; 的時候，幫我用 Children 來 Select 出 Target」&lt;/p&gt;
&lt;p&gt;註冊後就可以順利進行轉換了，讓我們直接用 Linqpad 試試：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pUSarcn.webp&#34;
  alt=&#34;Image&#34;width=&#34;956&#34; height=&#34;801&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果是比較複雜的狀況，也可以用實作&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2019/04/22/160442&#34;&gt;型別轉換器&lt;/a&gt;（&lt;code&gt;ITypeConverter&lt;/code&gt;）的方式來處理。以這個例子來說，就可以：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// c實作從 Parent 轉換到 IEnumerable&amp;lt;Target&amp;gt; 的 ITypeConverter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ParentToTargetsTypeConverter&lt;/span&gt; : ITypeConverter&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 轉換方法本體&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Target&amp;gt; Convert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Parent parent,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IEnumerable&amp;lt;Target&amp;gt; targets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ResolutionContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        targets = parent.Children.Select(child =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Id = parent.Id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Val = child.Val
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; targets;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著再把轉換器註冊到 &lt;code&gt;ConvertUsing&lt;/code&gt; 上，丟實體或是泛型都行：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//cfg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    .CreateMap&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    .ConvertUsing(new ParentToTargetsTypeConverter());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// OR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cfg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .CreateMap&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .ConvertUsing&amp;lt;ParentToTargetsTypeConverter&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;測試轉換：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mJXRNXR.webp&#34;
  alt=&#34;Image&#34;width=&#34;640&#34; height=&#34;504&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;之後當遇到型別轉換的過程並非單純欄位一對一的時候，就可以使用 ConvertUsing 的方式來自行處理。&lt;/p&gt;
&lt;p&gt;最後附上測試用的 Linqpad Code &lt;del&gt;方便我之後回來複製&lt;/del&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Sut().Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Target&amp;gt; Sut()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; boo = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Parent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Id = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Children = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Child&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Child { Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Child { Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = CreateMapperConfig().CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = mapper.Map&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;(boo);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; MapperConfiguration CreateMapperConfig()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 使用 Lambda&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//cfg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    .CreateMap&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    .ConvertUsing(parent =&amp;gt; parent.Children.Select(child =&amp;gt; new Target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//        Id = parent.Id,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//        Val = child.Val&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    }));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 使用 TypeConverter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//cfg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    .CreateMap&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    .ConvertUsing(new ParentToTargetsTypeConverter());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cfg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .CreateMap&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .ConvertUsing&amp;lt;ParentToTargetsTypeConverter&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; config;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ParentToTargetsTypeConverter&lt;/span&gt; : ITypeConverter&amp;lt;Parent, IEnumerable&amp;lt;Target&amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Target&amp;gt; Convert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Parent parent,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IEnumerable&amp;lt;Target&amp;gt; targets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ResolutionContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        targets = parent.Children.Select(child =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Id = parent.Id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Val = child.Val
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; targets;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Parent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Child&amp;gt; Children { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Child&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jianshu.com/p/47054d92db2a&#34;&gt;AutoMapper 之自定义类型转换器(Custom Type Converters) - 简书&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/wulex/article/details/78654555&#34;&gt;AutoMapper官方文档(十一)【自定义类型转换器】_极客神殿 - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2019/04/22/160442&#34;&gt;[小菜一碟] C# 中一個古老的好物 - TypeConverter | 軟體主廚的程式料理廚房 - 點部落&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/46725911/automapper-map-single-object-with-list-of-objects-inside-to-just-list&#34;&gt;c# - Automapper: Map single object with list of objects inside to just list - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/18096034/possible-to-use-automapper-to-map-one-object-to-list-of-objects&#34;&gt;c# - Possible to use AutoMapper to map one object to list of objects? - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/56987748/use-automapper-to-map-single-object-into-a-list-of-objects&#34;&gt;c# - use automapper to map single object into a list of objects - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;相關文章&#34;&gt;相關文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper&#34;&gt;AutoMapper —— 類別轉換超省力&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧</title>
      <link>https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/</link>
      <pubDate>Sun, 28 Nov 2021 20:13:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2XYv7X2.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是俺整理公司新訓內容的第六篇文章，目標是&lt;strong&gt;紀錄什麼是依賴注入（Dependency Injection）&lt;/strong&gt;。包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection#%E7%82%BA%E4%BB%80%E9%BA%BC%E9%9C%80%E8%A6%81%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5&#34;&gt;為什麼要依賴注入&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection#%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5%E7%9A%84%E7%A8%AE%E9%A1%9E&#34;&gt;依賴注入的種類（建構式注入、屬性注入、方法注入）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection#%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5%E7%9A%84%E4%B8%89%E7%A8%AE%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F-transientscopedsingleton&#34;&gt;.net Core 中依賴注入的生命週期&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;並用 &lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection#%E5%AF%A6%E4%BD%9C&#34;&gt;.net Core 實際跑一次依賴注入&lt;/a&gt;，&lt;strong&gt;藉由將控制權轉移給注入容器，解除分層與分層間、類別與類別間的依賴和耦合關係，達到以介面分離實作的目標&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#前言&#34;&gt;前言&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#為什麼需要依賴注入&#34;&gt;為什麼需要依賴注入&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#依賴注入的種類&#34;&gt;依賴注入的種類&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#建構式注入&#34;&gt;建構式注入&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#方法注入&#34;&gt;方法注入&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#屬性注入&#34;&gt;屬性注入&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#組合根composition-root&#34;&gt;組合根（Composition Root）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#net-core-中的依賴注入&#34;&gt;.Net Core 中的依賴注入&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-addscoped-來註冊介面對應的實作類別&#34;&gt;使用 AddScoped 來註冊介面對應的實作類別&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-addscoped-和委派來註冊介面對應的實作類別的產生方法&#34;&gt;使用 AddScoped 和委派來註冊介面對應的實作類別的產生方法&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#依賴注入的三種生命週期-transientscopedsingleton&#34;&gt;依賴注入的三種生命週期 Transient、Scoped、Singleton&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#延伸閱讀&#34;&gt;延伸閱讀&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#補充net-core-使用-buildserviceprovider-會建立多個實體&#34;&gt;補充：.Net Core 使用 BuildServiceProvider 會建立多個實體&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#實作&#34;&gt;實作&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#開始重構為建構式注入&#34;&gt;開始重構為建構式注入&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#補充從-appsettingsjson-取得組態&#34;&gt;補充：從 appsettings.json 取得組態&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#補充組合根請稍作分類&#34;&gt;補充：組合根請稍作分類&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#驗證&#34;&gt;驗證&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#本系列文章&#34;&gt;本系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;西元前的某一天，憂心的皇帝在朝堂內繞著柱子走，正巧被路過的廷尉看見。&lt;/p&gt;
&lt;p&gt;廷尉：「敢問陛下在煩惱什麼呢？」&lt;/p&gt;
&lt;p&gt;皇帝：『朕這是在想封賞的事兒哪。前朝之所以覆滅，根本的原因就在於大肆封賞臣下，四處分封土地給他們做諸侯。&lt;/p&gt;
&lt;p&gt;這些諸侯，肆意起用自己喜歡的人擔任要職、結黨營私，心情好就 &lt;code&gt;new 將軍(&amp;quot;我ㄉ朋友&amp;quot;);&lt;/code&gt;，十天就封了十個將軍。這些人若犯了錯，要處理他們還得看諸侯面子；而諸侯一聲令下，這些人便群起造反。&lt;/p&gt;
&lt;p&gt;並且，這些諸侯之間彼此喜歡直接往來，動不動就在自家裡下命令給 &lt;code&gt;隔壁諸侯.借糧草(100)&lt;/code&gt;，哪天就變成 &lt;code&gt;隔壁諸侯.揪團造反()&lt;/code&gt;。彼此之間偷來暗去，實在難以掌握。&lt;/p&gt;
&lt;p&gt;最後呢，一個逆賊起來造反，若要將他給辦了，附近諸侯就一起響應，每個都一齊報錯，Exception 成千上百，國家也就這樣滅了，想到這朕就頭痛得很，不知愛卿可有法子？』&lt;/p&gt;
&lt;p&gt;廷尉想了一想，便說：「陛下，此事要點還是在於諸侯之間&lt;strong&gt;相互依賴、彼此耦合&lt;/strong&gt;，致生禍端。&lt;/p&gt;
&lt;p&gt;臣有一計，先收回諸侯的人事任命權，使其不可私自 &lt;code&gt;new&lt;/code&gt; 自己人，所有人事異動，須&lt;strong&gt;由中央進行管理與派遣&lt;/strong&gt;。這樣即使諸侯要造反，也不知道下面這群打工仔是不是自己人。大家各司其職，諸侯做好自己的行政作業，打工仔派到崗位就做好自己的工作，彼此不直接依賴，這樣出事的機率就少了。&lt;/p&gt;
&lt;p&gt;其次，明令禁止諸侯私自往來，對諸侯們進行隔離，若是有公務上的需要，&lt;strong&gt;一律藉由中央提供的接口來溝通&lt;/strong&gt;，彼此之間明訂契約，由中央進行隔離與調派，諸侯間就只需要按照協議好的合約下去合作，這樣勾結的機會也就少了，耦合也就降低了。陛下覺得如何？」&lt;/p&gt;
&lt;p&gt;皇帝大喜：『如此甚好！治眾如治寡，在於分而治之。此計可有名字？』&lt;/p&gt;
&lt;p&gt;「此乃－－依賴注入之計！」&lt;/p&gt;
&lt;h3 id=&#34;為什麼需要依賴注入&#34;&gt;為什麼需要依賴注入&lt;/h3&gt;
&lt;p&gt;各位好，我們前面引用了民明書坊的《朕的郡縣制哪有這麼耦合》，相信各位對依賴注入應該已經有初步的了解了。說到依賴注入的觀念，就得先從 SOILD 中的依賴反轉原則開始談。&lt;/p&gt;
&lt;p&gt;這部份我們之前在&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;依賴反轉原則篇&lt;/a&gt;已經有詳細的說明，基於江湖道義，接下來就引用該篇的例子來快速帶過一下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：想好好了解的朋友，也可以從依序閱讀這幾篇相關文章後再回到這篇呦：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;讓我們從之前依賴反轉的範例開始吧：假設現在有間小小公司，老闆請來了小明當工程師，並請他開工撰寫產品程式碼。&lt;/p&gt;
&lt;p&gt;當「撰寫產品程式」對「工程師」直接依賴的時候，狀況可能是這樣的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work() &lt;span style=&#34;color:#75715e&#34;&gt;// 撰寫產品程式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Ming programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;過一陣子，老闆發現小明寫出來的東西似乎不太行，於是把小明趕走，另外請了小華。這時候因為用到的類別不一樣了，我們就必須要改一次程式碼：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Hua programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Hua(); &lt;span style=&#34;color:#75715e&#34;&gt;// 把小明改成小華&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;!--more--&gt;
&lt;p&gt;又過了好一陣子，老闆又另外請了小美來工作。於是又要再改一次，而且小美的工作方式甚至不叫做 &lt;code&gt;Programming&lt;/code&gt;，而是 &lt;code&gt;Coding&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Mei programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Mei(); &lt;span style=&#34;color:#75715e&#34;&gt;// 把小華改成小美&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Coding(); &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫方法也要改&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在有感覺到一點問題了嗎？如果一直換人，&lt;code&gt;Work&lt;/code&gt; 的程式碼豈不是每次都要修改？&lt;/p&gt;
&lt;p&gt;但我們平常開發程式的思維，會習慣從大範圍到小細節、從抽象到具體、從整體目標逐漸拆解成各個步驟的方向去處理，也就是從高階模組往低階模組的方向設計。&lt;/p&gt;
&lt;p&gt;例如說我們需要「會員查詢」功能，才用「DB 連線方法」和「資料篩選方法」等具體方式去達成我們要「會員查詢」這個目標。&lt;/p&gt;
&lt;p&gt;然而以上面工程師的例子來看：低階模組的變更，卻會導致使用它的高階模組連帶受到影響，在我們決定大方向大目標的時候卻被實作細節綁手綁腳，實在是很怪的一件事，和我們上述的習慣是相悖的。&lt;/p&gt;
&lt;p&gt;而依賴的低階模組越多，會被影響的機會就越高、修改的範圍和頻率也會急遽地拉高，變得無法掌握修改程式碼時影響的範圍，最終導致架構變得不穩固、程式碼到處都是不健康的&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;耦合&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;面對這樣的困境，依賴反轉原則告訴我們：&lt;br/&gt;&lt;strong&gt;高階模組不應該依賴於低階模組。兩者都應該依賴抽象。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也就是指，我們不要讓高階模組直接去依賴低階模組，而是使用抽象的、具有契約精神的&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt;來對他們進行隔離。&lt;/p&gt;
&lt;p&gt;如此一來，只要介面的契約成立了，高階模組就可以專心做好自己的事情（&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;職責&lt;/a&gt;），而不用去管低階模組的方法名稱之類的鳥事、低階模組也只要專注在實作介面要求的契約內容就行了。&lt;/p&gt;
&lt;p&gt;在這裡的重要前提是，我們必須了解到：&lt;strong&gt;並不是高階模組去依賴低階模組，而是高階模組提出它需要的功能，低階模組去實作出這些功能、達成高階模組的目標&lt;/strong&gt;，這也比較接近我們開發程式時的思維。&lt;/p&gt;
&lt;p&gt;例如前面的會員查詢：我們並不是因為有「DB 的連線方法」和「處理會員資料的方法」所以才說「我們有這兩個東西欸，那我們來組成會員查詢功能吧」；而是「我們想做一個會員查詢功能，所以我們需要連線到 DB，然後對這些資料做篩選和處理」&lt;/p&gt;
&lt;p&gt;而用工程師的例子來看，應該是要這樣的：「老闆為了製造產品（高階模組的目標），開出了工程師的應徵條件（介面），而小明前來應徵（低階模組的實作）」&lt;/p&gt;
&lt;p&gt;如此一來，依賴就「反轉」了。&lt;/p&gt;
&lt;p&gt;原本是 &lt;code&gt;高階模組 → 低階模組&lt;/code&gt; 的關係，變成了 &lt;code&gt;高階模組 → 介面 ← 低階模組&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;現在讓我們把上面例子的「工程師」改成介面，如此一來就會變成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 工程師的介面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IProgrammer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 小明，一位工程師&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ming&lt;/span&gt; : IProgrammer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Programming() { &lt;span style=&#34;color:#75715e&#34;&gt;/* Work */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 這邊需要一名工程師，呼叫小明前來&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IProgrammer programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 根據介面的契約，工程師一定都有 Programming 方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊就會遇到我們介面篇結束時所問的問題：我們使用功能之前，必須先建立該類別的實例，也就是 &lt;code&gt;new Ming()&lt;/code&gt;，那麼，我們不就還是直接依賴了實作嗎？&lt;/p&gt;
&lt;p&gt;面對這個問題，大大們提出了許多個解決的方法，其中最常見的就是：&lt;strong&gt;控制反轉 (Inversion of Control, IoC)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;思路非常的簡單：我們把實例的建立和實例的使用切分開來就好了，讓建立的去建立、讓使用的去使用，&lt;strong&gt;不再是由高階模組去建立並控制低階模組，而是我們讓一個控制反轉中心去建立低階模組，然後高階模組要使用的時候再把這個低階模組交給高階模組使用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如此一來，控制權也跟著反轉過來了，高階模組從主動建立低階模組，變成被動接收低階模組；&lt;/p&gt;
&lt;p&gt;也就是從原先的：&lt;code&gt;高階模組 —(建立)→ 低階模組&lt;/code&gt;&lt;br/&gt;變成了：&lt;code&gt;高階模組 ←(傳遞低階模組)— 控制反轉中心&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;也就是說，高階模組再也不需要關心如何建立，該建立哪個實體，只專注於使用功能，真正達到介面的精神。低階模組也只需要等待控制反轉中心分發，到了崗位就把份內事做好，專心在自己的職責身上即可。如此一來就能解除兩者之間的耦合。&lt;/p&gt;
&lt;p&gt;但是，要怎麼把控制中心建立的低階模組，交給高階模組做使用呢？這時候的實作方式就是我們今天的主角：&lt;strong&gt;依賴注入 (Dependency Injection)&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;依賴注入的種類&#34;&gt;依賴注入的種類&lt;/h2&gt;
&lt;p&gt;白話一點來說，「注入」也就是「丟進去」的意思。所以&lt;strong&gt;依賴注入就是指用各種方法把低階模組丟到高階模組裡&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;主要常見的有三種作法：建構式注入、方法注入、屬性注入。也就是從建構式丟進去、從方法丟進去、從屬性丟進去。&lt;/p&gt;
&lt;h3 id=&#34;建構式注入&#34;&gt;建構式注入&lt;/h3&gt;
&lt;p&gt;建構式注入顧名思義就是&lt;strong&gt;從建立物件時的建構式進行注入&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;現在假設我們有個「法師」的類別，並且它有個屬性用來表示他目前裝備的法術：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Wizard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 只有建構式時會給值，所以可以順手加上 readonly 防止被變動&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ISpell _spell;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 在建構式決定要裝備什麼法術&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Wizard(ISpell spell)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._spell = spell;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ISpell&lt;/span&gt; { } &lt;span style=&#34;color:#75715e&#34;&gt;// 法術介面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而建立物件時也使用建構式來傳遞：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; spell = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Fireball();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wizard = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Wizard(spell);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Fireball&lt;/span&gt; : ISpell { } &lt;span style=&#34;color:#75715e&#34;&gt;// 火球術&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由於建構式注入比較符合封裝的「管控邊界」精神、能明確地讓維護者一看就知道哪些東西會被注入，因此&lt;strong&gt;絕大部分的時候都應該使用建構式注入&lt;/strong&gt;，只有特殊情況可以使用方法注入和屬性注入。而到了 .Net Core 的時代，預設的 DI 容器更是只提供建構式注入。所以理想的情況下，建構式注入應該要是最熟悉的注入方式。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：要注意，對建構式做多載可能會造成 DI 容器混淆，不知道要選哪個建構式才好。因此設計類別時盡量以一個建構式為主，或是先了解一下使用的 DI 容器有沒有特別的處理方式再決定。&lt;/p&gt;
&lt;p&gt;關於在 .Net Core 裡面對多個建構式的對象註冊 DI 的作法，可以參閱黑大的這篇 &lt;a href=&#34;https://blog.darkthread.net/blog/aspnet-core-di-multi-constructors/&#34;&gt;ASP.NET Core DI 之多建構式問題&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;方法注入&#34;&gt;方法注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;方法注入適用於「呼叫方法時需要注入不同的依賴對象」時&lt;/strong&gt;。例如說該方法在不同地方被呼叫的依賴對象不一樣，又或者是第一次呼叫和第二次呼叫時的依賴對象不一樣。&lt;/p&gt;
&lt;p&gt;這時候我們就可以&lt;strong&gt;在呼叫方法的時候才把依賴對象一起丟進去&lt;/strong&gt;，讓使用端來決定要注入什麼。&lt;/p&gt;
&lt;p&gt;例如說我們的法師可以隨身攜帶法術卷軸，使用卷軸就可以放出對應的法術，因此法師類別就會有一個使用法術卷軸的方法。而我們想要等到施法的時候再決定要用哪個卷軸的咒語，這時候就可以把這件事情交給外部決定：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Wizard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 施放指定的法術卷軸&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Enchant(ISpellScroll scroll)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        scroll.CastSpell(); &lt;span style=&#34;color:#75715e&#34;&gt;// 詠唱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 法術卷軸介面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ISpellScroll&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; CastSpell();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那我們等到實際使用（使用卷軸施法）的時候再把依賴對象（卷軸）丟進去即可&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wizard = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Wizard();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; scroll = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; LevitationCharmScroll();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	wizard.Enchant(scroll); &lt;span style=&#34;color:#75715e&#34;&gt;// 使用卷軸施放漂浮咒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 漂浮咒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LevitationCharmScroll&lt;/span&gt; : ISpellScroll
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; CastSpell() {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一來就可以處理一些每次操作依賴對象都會不同的狀況了。但在使用方法注入的時候要注意：由於該方法的呼叫端需要準備依賴對象給方法當作參數使用，整個過程是在方法被呼叫的時候才動態處理的，所以呼叫端還是需要想辦法弄到該依賴對象，也就是再從上一層注入或是工廠製造之類的。&lt;/p&gt;
&lt;p&gt;在這個過程中就會增加類別或介面之間的依賴關係、並且讓注入的位置散落在各地等等，維護的時候就必須多注意一下。&lt;/p&gt;
&lt;p&gt;我個人比較常在一些輔助工具，例如擴充方法或是 Helper 看到方法注入，像這類單純操作邏輯的場合，就可以考慮採用方法注入的方式去處理。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：&lt;strong&gt;絕對不要把方法注入的依賴對象留在物件內部給其他方法使用&lt;/strong&gt;，例如Ａ方法注入了某個物件，保留給之後呼叫Ｂ方法的時候用，這樣容易造成時序耦合（Temporal Coupling）的問題，也就是使用者（通常是後續的維護人員）如果沒照你想好的順序呼叫這些方法的話，服務就會直接死去。&lt;/p&gt;
&lt;p&gt;如果使用者不清楚這些方法之間的關係，就很容易踩到地雷，而這樣挖坑的行為很明顯違反了封裝精神，並且也容易產生預期外的副作用。如果真的有需要針對依賴對象做初始化，還是考慮用建構式注入吧。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;屬性注入&#34;&gt;屬性注入&lt;/h3&gt;
&lt;p&gt;接著讓我們來看看屬性注入，顧名思義就是&lt;strong&gt;從公開的屬性丟進去&lt;/strong&gt;，因此也會被叫做設值注入。&lt;/p&gt;
&lt;p&gt;通常我們會在 &lt;strong&gt;「外部使用者要能夠隨時切換依賴對象」或是「類別已經有預設值了，但希望提供使用者可以覆寫掉預設值的彈性」時用到屬性注入&lt;/strong&gt;，例如說我們的法師同時只能裝備／記得一個法術，預設是火球術，但同時我們又想要可以從外部來替換裝備中的法術：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Wizard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 因為屬性注入的特性，請不要 readonly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; ISpell _spell; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 提供屬性給外部控制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; ISpell Spell
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        { 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 屬性注入的時候要注意: 如果沒有預設值很容易會發生錯誤&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._spell &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._spell = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Fireball();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._spell; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        { 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._spell = &lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ISpell&lt;/span&gt; {} &lt;span style=&#34;color:#75715e&#34;&gt;// 法術介面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Fireball&lt;/span&gt; : ISpell { } &lt;span style=&#34;color:#75715e&#34;&gt;// 火球術&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用的時候就可以直接對屬性賦值，例如我們現在有個法師就不想用火球術，而是使用邪王炎殺黑龍波：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wizard = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Wizard();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; spell = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DragonOfTheDarknessFlame();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    wizard.Spell = spell;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DragonOfTheDarknessFlame&lt;/span&gt; : ISpell { } &lt;span style=&#34;color:#75715e&#34;&gt;// 邪王炎殺黑龍波&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我個人是覺得屬性注入的範例其實就是使用介面的封裝小範例啦…。&lt;/p&gt;
&lt;p&gt;提到封裝，由於使用者並不會知道物件內部的狀態，所以屬性注入沒有提供預設值的話就很容易壞掉，但給預設值的時候又不可避免地產生耦合（例如上例的法師為了預設是火球術，所以和火球術產生了耦合）&lt;/p&gt;
&lt;p&gt;因此如果有為了將來的可擴充性而設計成「&lt;strong&gt;預設值使用內建的、通常會存在的類別，但允許外部隨時替換&lt;/strong&gt;」，也就是讓呼叫端決定「要不要」依賴的場合，再考慮使用會比較合適。否則一律推薦建構式注入。&lt;/p&gt;
&lt;h2 id=&#34;組合根composition-root&#34;&gt;組合根（Composition Root）&lt;/h2&gt;
&lt;p&gt;認識完注入的方式之後，讓我們來聊聊組合根吧。前面的例子可以注意到：即使我們要注入法術給法師，也還是要在呼叫端建立法術的實體。而如果呼叫端也使用依賴注入，就會需要呼叫端的呼叫端來注入實體。層層反轉之下，最終就會有一個地方來注入和分配全部的實體給各個物件。&lt;/p&gt;
&lt;p&gt;這也就是我們在前面提到的：&lt;strong&gt;我們會需要一個負責把各個材料注入到需要的類別中的「控制反轉中心」－－也就是叫做「組合根」的部份。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由於每一個類別會將依賴的對象交給外部，也就是呼叫者去決定。而呼叫者又會再往上拋給它的呼叫者，如此不斷往外推之後，就會集中到整個應用程式的啟動點，如此一來我們也就必須在啟動點進行依賴關係的處理。&lt;/p&gt;
&lt;p&gt;因此這個組合根的位置通常會盡可能地靠近程式的啟動點，例如整個應用程式 Startup 的地方，或是各個 DI 容器最終註冊的部分。在我們的實作例子中，也就是 API 的專案部份。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：請注意，組合根並不一定是在展示層，並且從「為了管理依賴關係而產生組合根」和「為了切分職責而產生展示層」是不同的觀點，不能混為一談。退一步說，我們也可能會把啟動點、組合根之類的切分出去，減少展示層的耦合。所以還是要看專案架構怎麼設計的才能確定組合根的位置。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;同時，由於組合根必須分配各個類別前往自己負責的崗位，因此它可以說是&lt;strong&gt;和所有模組都有直接依賴的關係&lt;/strong&gt;（並且也應該只有組合根可以知道整體的物件關聯）。因此，我們應該要在應用程式啟動的部份，找個風水寶地去統一管理我們的組合根和依賴關係，而不能讓注入的部分散亂在各地。&lt;/p&gt;
&lt;p&gt;不過這個部份現在的 DI 容器都已經處理好了，例如 Unity 的 UnityConfig、.net Core 的 ConfigureServices 等等，所以只要注意別在其他地方偷偷搞注入、挖坑給別人跳就好囉。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：不要以為真的遇不到……同事維護的專案就有遇到前同事直接在單元測試案例裡把 DI 容器叫出來註冊依賴注入的，都不知道從哪裡開始吐槽囧&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;如果在別的地方去亂對依賴關係動手動腳，很可能就會踩到一些坑。總之，盡量別在組合根以外的地方使用 DI 容器。&lt;/p&gt;
&lt;h2 id=&#34;net-core-中的依賴注入&#34;&gt;.Net Core 中的依賴注入&lt;/h2&gt;
&lt;p&gt;接著讓我們回到本系列的專案吧。可喜可賀的是：&lt;strong&gt;在 .net Core 的時代，依賴注入已經是內建提供的功能了！&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：使用 .net Framework 的朋友也不用擔心，可以使用 Unity, AutoFac 這幾個猛猛的 IoC 容器。教學文章也是網路一抓一大把那種。&lt;/p&gt;
&lt;p&gt;甚至到了預設使用建構式注入的 .net Core 時代，還是不少人會為了要用動態代理之類的花式注入手段而把 Autofac 裝回來呢。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;我們得有一個地方來做我們的組合根，註冊介面和實體的關係，並讓它可以將實體注入進來。也就是要告訴應用程式：「某某介面 對應的就是 某某實作，請幫我在需要到的時候丟進來。」&lt;/p&gt;
&lt;p&gt;在 .Net Core 中，&lt;strong&gt;我們可以在 &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;ConfigureServices&lt;/code&gt; 註冊我們需要的服務&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 這邊可能還有其他註冊的服務，Swagger 之類的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;例如在先前的 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;Swagger&lt;/a&gt; 我們就在這裡用套件提供的 &lt;code&gt;AddSwaggerGen()&lt;/code&gt; 註冊過 Swagger UI 服務。&lt;/p&gt;
&lt;h3 id=&#34;使用-addscoped-來註冊介面對應的實作類別&#34;&gt;使用 AddScoped 來註冊介面對應的實作類別&lt;/h3&gt;
&lt;p&gt;而當我們要註冊我們的介面和實作時，例如說我們有一個 &lt;code&gt;ITestService&lt;/code&gt; 的介面，希望告訴 DI 容器對應的實作是 &lt;code&gt;TestService&lt;/code&gt;，我們就可以&lt;strong&gt;使用 &lt;code&gt;AddScoped&amp;lt;&amp;gt;&lt;/code&gt; 來註冊對應關係&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 註冊 ITestService 的實作為 TestService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;ITestService, TestService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一來，當 DI 容器發現要注入 &lt;code&gt;ITestService&lt;/code&gt; 的場合，就會替我們建構 &lt;code&gt;TestService&lt;/code&gt; 並注入。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Y5BexuX.webp&#34;
  alt=&#34;Image&#34;width=&#34;1756&#34; height=&#34;1141&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;例如說我們的 &lt;code&gt;TestController&lt;/code&gt; 的建構式部分如下，可以看到我們有使用建構式注入：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; TestController(ITestService testService)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._testService = testService;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;當 &lt;code&gt;TestController&lt;/code&gt; 建立的時候，DI 容器就會知道需要 &lt;code&gt;ITestService&lt;/code&gt; 來注入，並找到我們註冊的 &lt;code&gt;TestService&lt;/code&gt;
來注入到 &lt;code&gt;TestController&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這時候如果 &lt;code&gt;TestService&lt;/code&gt; 也有需要注入的依賴對象，DI 容器就會再回來找我們註冊對應的實作，依此類推，不斷&lt;strong&gt;遞迴&lt;/strong&gt;下去，直到注入都完成為止。&lt;/p&gt;
&lt;h3 id=&#34;使用-addscoped-和委派來註冊介面對應的實作類別的產生方法&#34;&gt;使用 AddScoped 和委派來註冊介面對應的實作類別的產生方法&lt;/h3&gt;
&lt;p&gt;有些朋友可能會有疑問：我的物件建立時還需要做一些處理才能建立，沒辦法直接告訴 &lt;code&gt;AddScoped&lt;/code&gt; 就完事了。&lt;/p&gt;
&lt;p&gt;不用擔心，&lt;code&gt;AddScoped&lt;/code&gt; 也提供了委派的做法，讓我們可以直接告訴 DI 容器這個實作的產生方法，這個產生過程中我們就能進行一些操作，現在讓我們來示範一次。&lt;/p&gt;
&lt;p&gt;假設我們的 &lt;code&gt;TestService&lt;/code&gt; 必須要傳遞一個服務 Token 的字串進去，就會像是這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;ITestService&amp;gt;(sp =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; token = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;TestServiceToken&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TestService(token);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那如果不只是字串這種寫死的狀況，而是我們基於一些原因，想要指定拿到註冊中其他服務的實作的話，就可以使用委派傳入的 ServiceProvider 來取得目前註冊的內容，例如這個 token 其實是另一個 &lt;code&gt;ITokenService&lt;/code&gt; 提供的話：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;ITestService&amp;gt;(sp =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; tokenService = sp.GetRequiredService&amp;lt;ITokenService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; token = tokenService.Get();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TestService(token);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，我們能&lt;strong&gt;藉由 &lt;code&gt;ServiceProvider&lt;/code&gt; 的 &lt;code&gt;GetRequiredService&lt;/code&gt; 這個方法來取得其他註冊的實體&lt;/strong&gt;，並利用這個實體來完成注入所需的材料。&lt;/p&gt;
&lt;h3 id=&#34;依賴注入的三種生命週期-transientscopedsingleton&#34;&gt;依賴注入的三種生命週期 Transient、Scoped、Singleton&lt;/h3&gt;
&lt;p&gt;除了 &lt;code&gt;AddScoped()&lt;/code&gt; 以外，.net Core 還提供了另外兩種注入方法：&lt;code&gt;AddTransient()&lt;/code&gt; 和 &lt;code&gt;AddSingleton()&lt;/code&gt;，他們對應的是三種不同的生命週期：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transient（一次性）&lt;/strong&gt;：每次注入都建立一個新的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoped（作用域）&lt;/strong&gt;：每次 Request 都建立一個新的，同個 Request 重複利用同一個&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Singleton（單例）&lt;/strong&gt;：只建立一個新的，每次都重複利用同一個&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假設我們有一個 &lt;code&gt;ILogger&lt;/code&gt; 類別，專門幫我們寫 Log。然後我們的 API 會經過 &lt;code&gt;TestController&lt;/code&gt;、&lt;code&gt;TestService&lt;/code&gt;、&lt;code&gt;TestRepository&lt;/code&gt; 這三層物件去查詢資料，其中每一層都注入了 &lt;code&gt;ILogger&lt;/code&gt;。那麼：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transient&lt;/strong&gt;：每一層物件都有自己的、全新的 &lt;code&gt;ILogger&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoped&lt;/strong&gt;：同一次 API 呼叫裡的每一層物件都是用同一個 &lt;code&gt;ILogger&lt;/code&gt;，等到下一次呼叫才建立新的  &lt;code&gt;ILogger&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Singleton&lt;/strong&gt;：不論哪次呼叫、不論哪一層注入，所有人都共用同一個 &lt;code&gt;ILogger&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一般來說最常用的會是 Scoped，例如功能服務或登入者資訊，在同一次呼叫中保持同一個即可。但面對 HttpCilent 這類能共用同個實例節省資源的，我們就可以考慮使用 Singleton。這邊就再請各位按照使用場景來決定該用哪種生命週期。&lt;/p&gt;
&lt;p&gt;另外，注入時請注意生命週期的範圍。例如註冊為 Singleton 的類別不能依賴註冊為 Scoped 的類別，因為如果大家一起用的 Singleton 程式跑到一半，綁在 Request 的 Scoped 依賴對象先消失了，問題可就大了，不可不慎。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於 HttpClient 的部份，更棒的做法是使用 HttpClientFactory。可以參照：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/httpclient-sigleton/&#34;&gt;HttpClient，該 using 還是 static? - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0&#34;&gt;在 ASP.NET Core 中使用 IHttpClientFactory 發出 HTTP 要求 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;關於 Singleton 的部份，有興趣的朋友可以了解看看。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://linziyou.info/2020/11/10/%E5%96%AE%E4%BE%8B%E6%A8%A1%E5%BC%8F-singleton-pattern/&#34;&gt;單例模式 Singleton Pattern – LinZiyou Dev Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E5%93%88%E5%98%8D-%E4%B8%96%E7%95%8C/singleton-pattern-%E4%BB%8B%E7%B4%B9-%E6%98%AF%E5%90%A6%E7%82%BA-anti-pattern-1c685a1d7134&#34;&gt;Singleton Pattern 介紹，是否為 Anti-pattern？ | by Camel | 嗨，世界&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;延伸閱讀&#34;&gt;延伸閱讀&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.johnwu.cc/article/ironman-day04-asp-net-core-dependency-injection.html&#34;&gt;ASP.NET Core 2 系列 - 依賴注入 (Dependency Injection) | John Wu&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/aspnet-core-di-notes/&#34;&gt;不可不知的 ASP.NET Core 依賴注入 - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.gss.com.tw/blog/net-core-service-lifetime&#34;&gt;.Net Core 服務存留期 (Service Lifetime)：叡揚部落格&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://raychiutw.github.io/2019/ASP-Net-Core-DI-%E5%AE%B9%E5%99%A8%E4%B8%AD-Service-%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F/&#34;&gt;ASP.Net Core DI 容器中 Service 生命週期 | Ray&amp;rsquo;s Notes&lt;/a&gt;&amp;gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;補充net-core-使用-buildserviceprovider-會建立多個實體&#34;&gt;補充：.Net Core 使用 BuildServiceProvider 會建立多個實體&lt;/h3&gt;
&lt;p&gt;我們在前面提到過藉由 &lt;code&gt;AddScoped&lt;/code&gt; 傳入委派的 ServiceProvider 的 &lt;code&gt;GetRequiredService&lt;/code&gt; 方法來取得其他註冊的實體這個做法。&lt;/p&gt;
&lt;p&gt;那可能就有一些比較聰明的朋友，知道 ServiceProvider 能拿到其他註冊的實體之後，為了在沒有 ServiceProvider 的地方也能取得其他實體（例如想直接在 &lt;code&gt;ConfigureServices&lt;/code&gt; 就直接拿到實體，然後經過處理再提供給多個注入使用等等）&lt;/p&gt;
&lt;p&gt;所以 Google 了一下怎麼弄出個 ServiceProvider，就用了 &lt;code&gt;BuildServiceProvider&lt;/code&gt; 來建立一個 ServiceProvider，但這實際上是相當危險的。&lt;/p&gt;
&lt;p&gt;因為 &lt;code&gt;BuildServiceProvider&lt;/code&gt; 建立的是一個全新的 ServiceProvider，並非注入時 DI 容易幫我們建來使用的那一個 ServiceProvider，這樣就會造成有兩個 ServiceProvider 在場的狀況。&lt;/p&gt;
&lt;p&gt;如此一來如果使用一些 Singleton 的服務，可能就會產生預期外的結果。因此建議還是乖乖在 &lt;code&gt;AddScoped&lt;/code&gt; 之類的方法內使用委派的 ServiceProvider 比較好。&lt;/p&gt;
&lt;p&gt;參考資料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.johnwu.cc/article/asp-net-core-3-build-service-provider.html&#34;&gt;ASP.NET Core 3 系列 - 自行建置 Service Provider | John Wu&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/56042989/what-are-the-costs-and-possible-side-effects-of-calling-buildserviceprovider-i&#34;&gt;c# - What are the costs and possible side effects of calling BuildServiceProvider() in ConfigureServices() - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;實作&#34;&gt;實作&lt;/h2&gt;
&lt;p&gt;現在我們稍微了解了 .net Core 裡的注入方式，接著就延續我們在 &lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;分層架構&lt;/a&gt; 篇的進度，來把依賴注入導入到我們本系列的 ProjectN 菜雞專案吧。在上一期，我們利用分層的概念將整個流程拆分成 Controller, Service, Repository 三個主要區塊。&lt;/p&gt;
&lt;p&gt;其中有直接依賴的部分，會在 Controller 銜接到 Service 以及 Service 銜接到 Repository 的部份。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardService&lt;/span&gt; : ICardService
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ICardRepository _cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardService()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以注意到我們在 &lt;code&gt;CardService&lt;/code&gt; 裡面直接 new 了 &lt;code&gt;CardRepository&lt;/code&gt; 來使用，這就是直接依賴。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：前篇分層時用到的 AutoMapper 套件的注入方式，請參見 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper#%E8%B5%B0%E5%90%91%E6%B3%A8%E5%85%A5&#34;&gt;AutoMapper#走向注入&lt;/a&gt;，此處暫且忽略。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;基於依賴反轉原則，我們會希望達成「CardService 依賴的是 ICardRepository 這個介面，並由 CardRepository 實作該介面，藉由介面隔離實作來解除耦合」的目標。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;對這個概念不太熟悉的朋友，可以參照 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt; 與 &lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;依賴反轉原則&lt;/a&gt; 的說明&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;開始重構為建構式注入&#34;&gt;開始重構為建構式注入&lt;/h3&gt;
&lt;p&gt;現在讓我們改成使用建構式注入吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardService&lt;/span&gt; : ICardService
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ICardRepository _cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardService(ICardRepository cardRepository)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository = cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著為了要讓 DI 容器知道 &lt;code&gt;ICardRepository&lt;/code&gt; 對應的實作是 &lt;code&gt;CardRepository&lt;/code&gt;，讓我們前往 &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;ConfigureServices&lt;/code&gt; 把它註冊起來：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 註冊 ICardRepository 的實作為 CardRepository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;ICardRepository, CardRepository&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 這邊可能還有其他註冊的服務，Swagger 之類的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;補充：請注意前面提到過的「組合根會直接依賴所有註冊的模組」因此這邊有需要的話記得把參考 using 補上呦。&lt;/p&gt;
&lt;p&gt;並且由於這個範例的組合根在展示層，因此和&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;上篇&lt;/a&gt;的三層式架構圖略有不同，展示層也會依賴到資料存取層，還請注意。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著讓我們如法炮製，把 Controller 裡直接依賴的 Service 也拆開吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ICardService _cardService;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardController(ICardService cardService)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService = cardService;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後補上註冊。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;ICardService, CardService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;ICardRepository, CardRepository&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// 這邊可能還有其他註冊的服務，Swagger 之類的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後再將前面系列用到的一些工具，例如 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper&#34;&gt;AutoMapper&lt;/a&gt;, &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper/&#34;&gt;Dapper&lt;/a&gt; 給注入好（這部分就請根據自己專案的內容調整囉），就完成啦。&lt;/p&gt;
&lt;p&gt;那我們前面有提到，可以用「告訴 DI 容器該物件的產生方法」來做一些額外的事情，這邊就讓我們優化一下。&lt;/p&gt;
&lt;p&gt;我們在先前的 &lt;code&gt;CardRepository&lt;/code&gt; 有用私有欄位來存放連線字串：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;seealso cref=&amp;#34;ProjectN.Repository.Interface.ICardRepository&amp;#34; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardRepository&lt;/span&gt; : ICardRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// 連線字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _connectString = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;Server=(LocalDB)\MSSQLLocalDB;Database=Newbie;Trusted_Connection=True;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在我們希望連線字串不要寫死在類別裡，而是用建構式注入的方式丟進去，就會變成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;seealso cref=&amp;#34;ProjectN.Repository.Interface.ICardRepository&amp;#34; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardRepository&lt;/span&gt; : ICardRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// 連線字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _connectString;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardRepository(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; connectString)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._connectString = connectString;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們回到註冊 &lt;code&gt;ICardRepository&lt;/code&gt; 的地方，因為 &lt;code&gt;CardRepository&lt;/code&gt; 的建構式現在必須要提供連線字串了，所以我們要改一下 &lt;code&gt;AddScoped&lt;/code&gt; 的寫法，把連線字串丟給它：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;ICardRepository&amp;gt;(sp =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectString = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;Server=(LocalDB)\MSSQLLocalDB;Database=Newbie;Trusted_Connection=True;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository(connectString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到&lt;strong&gt;我們用 &lt;code&gt;AddScoped&lt;/code&gt; 提供了 &lt;code&gt;ICardRepository&lt;/code&gt; 對應物件的產生方法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這樣 .net Core 就會知道要先執行裡面的委派與匿名函式 &lt;code&gt;sp =&amp;gt; {}&lt;/code&gt;，就能拿到 &lt;code&gt;CardRepository&lt;/code&gt; 來用囉！&lt;/p&gt;
&lt;h3 id=&#34;補充從-appsettingsjson-取得組態&#34;&gt;補充：從 appsettings.json 取得組態&lt;/h3&gt;
&lt;p&gt;另外補充一下，在連線字串這類字串的注入時，我個人偏好更進一步使用 &lt;code&gt;appsettings.json&lt;/code&gt;（以前 .net framework 時用過 &lt;code&gt;web.config&lt;/code&gt; 的朋友可能會比較熟）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於 appsettings.json 的用法這邊就不再贅述，想了解的朋友可以參考這幾篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2020/06/28/how_to_read_config_appsettings_json_via_net_core_31&#34;&gt;如何讀取 AppSettings.json 組態設定檔 | 余小章 @ 大內殿堂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/aspnet-core-practice-appsetting/&#34;&gt;ASP.NET Core 練習 - 使用 appSetting - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://marcus116.blogspot.com/2019/03/how-to-get-value-appsettingsjson-in-netcore.html&#34;&gt;如何取得 appsettings.json 組態設定 ~ m@rcus 學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;因此這邊就把連線字串丟到 &lt;code&gt;appsettings.json&lt;/code&gt;，增加一欄 &lt;code&gt;ConnectionString&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Logging&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;LogLevel&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Default&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Information&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Microsoft&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Warning&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Microsoft.Hosting.Lifetime&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Information&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;AllowedHosts&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;ConnectionString&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Server=(LocalDB)\\MSSQLLocalDB;Database=Newbie;Trusted_Connection=True;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著回到 &lt;code&gt;Startup&lt;/code&gt; 的建構式，建立一個連線字串的欄位 &lt;code&gt;_connectionString&lt;/code&gt; 然後把連線字串讀出來：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Startup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _connectionString;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Startup(IConfiguration configuration)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		Configuration = configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._connectionString = configuration.GetValue&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ConnectionString&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣我們就完成了 &lt;code&gt;Startup&lt;/code&gt; 的連線字串注入囉，只要再把這個字串提供給 &lt;code&gt;CardRepository&lt;/code&gt; 就行了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;ICardRepository&amp;gt;(sp =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository(_connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果沒有多個類別共用同一個連線方法的話，我們也可以直接從組態拿就行了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;ICardRepository&amp;gt;(sp =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; connectionString = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Configuration.GetValue&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ConnectionString&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository(connectionString);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;再請根據狀況靈活地運用 &lt;code&gt;Configuration.GetValue&lt;/code&gt; 吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充，也可以使用 &lt;a href=&#34;https://igouist.github.io/post/2024/08/dotnet-ioptions/&#34;&gt;IOption&lt;/a&gt; 的方式處理，或是包裝成一個專門提供的物件，像是 &lt;code&gt;IConnectionProvider&lt;/code&gt; 之類的，可以彈性地選擇做法。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;補充組合根請稍作分類&#34;&gt;補充：組合根請稍作分類&lt;/h3&gt;
&lt;p&gt;這邊補充一下，在實務上由於一個應用程式的注入可能有數十個，因此我們會稍微用註解或可摺疊的 &lt;code&gt;#Region&lt;/code&gt; 來分段一下，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;ICardService, CardService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;ICardRepository, CardRepository&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Others&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 或是使用 region，數量很多的時候就可以收攏&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#region&lt;/span&gt; -- Service --
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;ICardService, CardService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;#endregion&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;#region&lt;/span&gt; -- Repository --
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddScoped&amp;lt;ICardRepository, CardRepository&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;#endregion&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Others&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而前幾個月的時候，敝司某服務的註冊數量多到一個連滑鼠中鍵滾輪都會痛哭的程度。咱同事就利用對 &lt;code&gt;IServiceCollection&lt;/code&gt; 做擴充方法的方式，將組合根分類並切出去管理：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ProjectN.DIExtensions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// Service 相關註冊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ServiceDIExtensions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; IServiceCollection AddServices(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt; IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		services.AddScoped&amp;lt;ICardService, CardService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; services;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Startup.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddServices(); &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫 ServiceDIExtensions 進行註冊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddRepositories();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;雖然可以有效把註冊切分出去，但也會沒辦法在同個地方管理所有註冊，略麻煩，還是請真的有需要的時候再嘗試。這邊就當作分享這個做法給大家。&lt;/p&gt;
&lt;h3 id=&#34;驗證&#34;&gt;驗證&lt;/h3&gt;
&lt;p&gt;現在我們已經將三層的內容都改成使用注入了，最後就來測試一下是否有串接成功吧！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SFfOkFT.webp&#34;
  alt=&#34;Image&#34;width=&#34;918&#34; height=&#34;422&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;下中斷點，可以看到我們 CardService 要求的 ICardRepository 的確傳入了實作的 CardRepository&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1qS3DHL.webp&#34;
  alt=&#34;Image&#34;width=&#34;554&#34; height=&#34;435&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;也成功從資料庫中查詢到卡片了！打完收工～&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;這篇文章介紹了一些依賴注入的作法，並在 .net Core 的專案上進行實作。這邊就做個小總結：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;為什麼要依賴注入
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解除類別與類別間的直接耦合&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;由組合根來負責物件的建立和傳遞，更能讓依賴雙方專注於自己的職責&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;依賴注入常見的做法有
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;建構式注入&lt;/strong&gt;：把依賴對象從建構式扔進去&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;屬性注入&lt;/strong&gt;：把依賴對象從公開屬性扔進去&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方法注入&lt;/strong&gt;：呼叫方法時再把依賴對象當參數扔進去&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;.Net Core 中的依賴注入
&lt;ul&gt;
&lt;li&gt;已內建，我們可以在 &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;ConfigureServices&lt;/code&gt; 進行註冊&lt;/li&gt;
&lt;li&gt;.Net Core 中，依賴注入的生命週期有
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transient（一次性）&lt;/strong&gt;：每次注入都建立一個新的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoped（作用域）&lt;/strong&gt;：每次 Request 都建立一個新的，同個 Request 重複利用同一個&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Singleton（單例）&lt;/strong&gt;：只建立一個新的，每次都重複利用同一個&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後還是要叮嚀一下，依賴注入在實務上還會遇到許多眉角，不同的 DI 容器也會提供不同的方法，或是面對眼前的架構不知道從何拆起之類的，這些都是重構日常。&lt;/p&gt;
&lt;p&gt;例如說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.azurewebsites.net/yc421206/2021/05/21/how_to_register_the_same_interface_for_multiple_implement_microsoft_extensions_dependencyInjection&#34;&gt;要怎麼對一個介面註冊兩個實作，再根據狀況使用？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/22795459/is-servicelocator-an-anti-pattern&#34;&gt;是不是可以乾脆把 DI 容器注入到對象中再彈性取用（就像服務定位器）？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;等等的問題。&lt;/p&gt;
&lt;p&gt;但我個人認為只要有依賴反轉、隔離耦合這些依賴注入的基本觀念，剩下的就是了解工具如何使用、開 Google 查詢有哪些坑的步驟而已了。&lt;del&gt;然後上班一半時間都在 Google&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;除了&lt;strong&gt;藉由依賴注入去解耦合，避免讓類別成為控制狂&lt;/strong&gt;（就是指類別依賴的對象都自己 new 出來，從建立到生命週期都由類別自己控制，最終變成強耦合的狀況）以外，我們使用依賴注入的時候，有時會發生一些令人困惑的事情，這些其實就是讓我們動手重構的好幫手。&lt;/p&gt;
&lt;p&gt;例如說&lt;strong&gt;循環依賴&lt;/strong&gt;，當我們發現Ａ類別依賴了Ｂ類別，Ｂ類別又依賴Ａ類別而發生錯誤時，就可以考慮是不是能夠整合這兩個類別，例如抽出一個更高階的類別來統合這兩個類別的動作流程等等。&lt;/p&gt;
&lt;p&gt;又或是像&lt;strong&gt;過度注入&lt;/strong&gt;：當我們用建構式注入的時候，有時候會發現其中幾個類別的建構式會變得超級肥，可能光是傳入參數就多達二三十個等等。&lt;/p&gt;
&lt;p&gt;這時候其實就是一種警示：這個類別為什麼會依賴這麼多對象？是不是這個類別的&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;職責&lt;/a&gt;太多了？&lt;/p&gt;
&lt;p&gt;諸如此類，依賴注入其實可以&lt;strong&gt;幫助我們重新梳理我們類別的邊界和耦合關係&lt;/strong&gt;，讓我們注意到一些需要重構的徵象。這也是最近跟著同事重構的心得哪。&lt;/p&gt;
&lt;p&gt;那麼今天就先到這兒，祝各位解耦順利、斷開魂結。那麼，我們&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;下次&lt;/a&gt;見～&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789864344987&#34;&gt;依賴注入：原理、實作與設計模式 (Dependency Injection: Principles, Practices, Patterns, 2/e)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.johnwu.cc/article/ironman-day04-asp-net-core-dependency-injection.html&#34;&gt;[鐵人賽 Day04] ASP.NET Core 2 系列 - 依賴注入 (Dependency Injection) | John Wu&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.huanlintalk.com/2011/11/dependency-injection-5.html&#34;&gt;Dependency Injection 筆記 (5) - Huan-Lin 學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0&#34;&gt;NET Core 中的相依性插入 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2013/04/aspnet-mvc-part6-diioc-unitymvc.html&#34;&gt;mrkt 的程式學習筆記: ASP.NET MVC 專案分層架構 Part.6 - DI/IoC 使用 Unity.MVC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10229256&#34;&gt;DI(Dependency injection) 注入方式 - iT 邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5-didependency-injection/&#34;&gt;依賴注入 DI(Dependency Injection) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/daniel/2018/01/17/140435&#34;&gt;IOC(控制反轉) ， DI(依賴注入) 深入淺出~~ | 石頭的coding之路 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://notfalse.net/3/ioc-di&#34;&gt;控制反轉 (IoC) 與 依賴注入 (DI) - NotFalse 技術客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/su9257/article/details/115456338&#34;&gt;搬砖方法论：组合根(Composition Root)_su9257的博客-CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.azurewebsites.net/yc421206/2021/05/21/how_to_register_the_same_interface_for_multiple_implement_microsoft_extensions_dependencyInjection&#34;&gt;通過 Microsoft.Extensions.DependencyInjection，多個實作如何註冊相同的介面 | 余小章 @ 大內殿堂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jianshu.com/p/33ea3da8a5a2&#34;&gt;依赖注入DI的代替，服务定位器模式 - 简书&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其他參考資料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://harrypotter.fandom.com/wiki/Levitation_Charm&#34;&gt;Levitation Charm | Harry Potter Wiki | Fandom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://yuyuhakusho.fandom.com/wiki/Dragon_of_the_Darkness_Flame&#34;&gt;Dragon of the Darkness Flame (technique) | YuYu Hakusho Wiki | Fandom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E9%83%A1%E5%8E%BF%E5%88%B6#%E7%A7%A6%E5%B8%9D%E5%9B%BD%E4%BB%A5%E5%90%8E%E7%9A%84%E9%83%A1%E5%8E%BF%E5%88%B6&#34;&gt;秦帝國以後的郡縣制 - 維基百科&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;本系列文章&#34;&gt;本系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>在 Swagger UI 加上驗證按鈕，讓 Request Header 傳遞 Authorize Token</title>
      <link>https://igouist.github.io/post/2021/10/swagger-enable-authorize/</link>
      <pubDate>Sat, 16 Oct 2021 23:50:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/10/swagger-enable-authorize/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XjZLvSZ.webp&#34;
  alt=&#34;Image&#34;width=&#34;909&#34; height=&#34;286&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在先前的 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生簡單好看可測試的 API 文件吧&lt;/a&gt; 中，我們介紹了在 .net Core 環境使用 &lt;strong&gt;Swashbuckle&lt;/strong&gt; 套件來產生 Swagger 文檔，並且直接在 Swagger UI 中呼叫 API 來進行測試。&lt;/p&gt;
&lt;p&gt;但很多時候，我們的 API 會需要先驗證才能使用，例如&lt;strong&gt;在 Header 傳遞 Token 來驗證身分&lt;/strong&gt;等等。這時候 Swagger UI 就會整個廢掉，打了都會出錯，很不方便。&lt;/p&gt;
&lt;p&gt;因此這篇文章就紀錄一下如何在 Swagger UI 上加入 Authorize Token 的傳遞，讓 Swagger UI 在需要身分驗證的環境也能直接呼叫使用。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#環境佈置&#34;&gt;環境佈置&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#加入-authorize-設置&#34;&gt;加入 Authorize 設置&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#完工測試&#34;&gt;完工測試&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;環境佈置&#34;&gt;環境佈置&lt;/h2&gt;
&lt;p&gt;首先範例專案直接參考 The Will Will Web 的這篇 &lt;a href=&#34;https://blog.miniasp.com/post/2019/12/16/How-to-use-JWT-token-based-auth-in-aspnet-core-31&#34;&gt;如何在 ASP.NET Core 3 使用 Token-based 身分驗證與授權 (JWT)&lt;/a&gt;，捏一個&lt;strong&gt;需要登入取得 JWT Token，然後將 Token 放到 Header 的 Authorize 才能查詢資料&lt;/strong&gt;的專案。&lt;/p&gt;
&lt;p&gt;專案的大致狀況和目前 Swagger UI 如下，有登入和查詢兩支方法：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tfz8cIo.webp&#34;
  alt=&#34;Image&#34;width=&#34;599&#34; height=&#34;468&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;那我們沒有登入的情況直接呼叫查詢方法就會報錯：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/08QLPLM.webp&#34;
  alt=&#34;Image&#34;width=&#34;547&#34; height=&#34;387&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;登入的話就能拿到 Token：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ffzV49y.webp&#34;
  alt=&#34;Image&#34;width=&#34;549&#34; height=&#34;397&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;用 Postman 試試看把 Token 掛到 Authorization，查詢就可以成功：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fQhcIdC.webp&#34;
  alt=&#34;Image&#34;width=&#34;907&#34; height=&#34;496&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;但是我們的 Swagger 還沒有提供能放 Authorization Token 的地方，這樣用起來就會 Hen 不方便。&lt;/p&gt;
&lt;p&gt;因此目標就是：可以將這組 Token 放到 Header 裡，讓查詢方法不要報錯。讓我們開始吧！&lt;/p&gt;
&lt;h2 id=&#34;加入-authorize-設置&#34;&gt;加入 Authorize 設置&lt;/h2&gt;
&lt;p&gt;首先讓我們先找到註冊 Swagger 產生器的地方，以先前的 Swagger 介紹文為例的話，會是在  &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;ConfigureServices&lt;/code&gt; 裡的 &lt;code&gt;AddSwaggerGen&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;裡面可能已經有包含 API 簡介等欄位，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 註冊 Swagger 產生器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddSwaggerGen(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// API 服務簡介&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options.SwaggerDoc(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiInfo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Version = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JWT Demo&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞嘗試 JWT 的範例 API&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TermsOfService = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Contact = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiContact
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Igouist&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Email = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Empty,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/about/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 讀取 XML 檔案產生 API 說明&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlFile = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{Assembly.GetExecutingAssembly().GetName().Name}.xml&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options.IncludeXmlComments(xmlPath);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;首先讓我們用 Security Scheme 來告訴 Swagger 我們的驗證資訊吧。在 &lt;code&gt;AddSwaggerGen&lt;/code&gt; 中加上 &lt;code&gt;AddSecurityDefinition&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.AddSecurityDefinition(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiSecurityScheme
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Type = SecuritySchemeType.ApiKey,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Scheme = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        BearerFormat = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JWT&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        In = ParameterLocation.Header,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JWT Authorization&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;加了之後就能在 Swagger UI 看見我們的 Authorize 按鈕囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Opk7XZW.webp&#34;
  alt=&#34;Image&#34;width=&#34;472&#34; height=&#34;310&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;點開就會看到我們上面定義的內容：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SpvrFXG.webp&#34;
  alt=&#34;Image&#34;width=&#34;697&#34; height=&#34;375&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;不過現在還不會作用，我們還得讓全部的呼叫都自動加上這個 Token 才行。接著在 &lt;code&gt;AddSwaggerGen&lt;/code&gt; 中加上 &lt;code&gt;AddSecurityRequirement&lt;/code&gt;，並且讓他去抓我們前面設定好 &amp;ldquo;Bearer&amp;rdquo; 的 SecurityScheme：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.AddSecurityRequirement(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiSecurityRequirement
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiSecurityScheme
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Reference = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiReference
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Type = ReferenceType.SecurityScheme,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Id = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;[] {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意 &lt;code&gt;Id&lt;/code&gt; 要和我們上一步加入的 &lt;code&gt;Scheme&lt;/code&gt; 一致呦。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：OpenApiSecurityRequirement 是一個 Dictionary，所以中間那層 &lt;code&gt;{}&lt;/code&gt; 不要忘囉&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;加完之後現在的 &lt;code&gt;AddSwaggerGen&lt;/code&gt; 大概是這個樣子的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 註冊 Swagger 產生器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddSwaggerGen(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// API 服務簡介&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options.SwaggerDoc(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiInfo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Version = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JWT Demo&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞嘗試 JWT 的範例 API&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TermsOfService = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Contact = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiContact
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Igouist&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Email = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Empty,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/about/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Authorization&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options.AddSecurityDefinition(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiSecurityScheme
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Type = SecuritySchemeType.ApiKey,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Scheme = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            BearerFormat = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JWT&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            In = ParameterLocation.Header,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JWT Authorization&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options.AddSecurityRequirement(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiSecurityRequirement
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiSecurityScheme
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Reference = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiReference
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Type = ReferenceType.SecurityScheme,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Id = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;[] {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 讀取 XML 檔案產生 API 說明&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlFile = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{Assembly.GetExecutingAssembly().GetName().Name}.xml&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options.IncludeXmlComments(xmlPath);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;完工測試&#34;&gt;完工測試&lt;/h2&gt;
&lt;p&gt;接著就讓我們啟動試試吧！&lt;/p&gt;
&lt;p&gt;首先讓我們把登入的 Token 放到 Authorization 按鈕的欄位裡，不要忘記加上 Bearer：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/R0kbhqC.webp&#34;
  alt=&#34;Image&#34;width=&#34;668&#34; height=&#34;345&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TlChzMH.webp&#34;
  alt=&#34;Image&#34;width=&#34;670&#34; height=&#34;354&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;加入之後就 Close，讓我們打看看查詢的方法：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Jyyjqqe.webp&#34;
  alt=&#34;Image&#34;width=&#34;767&#34; height=&#34;384&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到成功拿到值啦！&lt;/p&gt;
&lt;p&gt;從開發工具也可以看到 Header 的確有加上 Bearer Token 了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7L0o40m.webp&#34;
  alt=&#34;Image&#34;width=&#34;833&#34; height=&#34;626&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;大功告成，打完收工！&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://marcus116.blogspot.com/2022/04/web-api-swagger-headers-api-token.html&#34;&gt;Swagger - 在 Headers 中新增 API Token 驗證 ~ m@rcus 學習筆記 (marcus116.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/43447688/setting-up-swagger-asp-net-core-using-the-authorization-headers-bearer&#34;&gt;c# - Setting up Swagger (ASP.NET Core) using the Authorization headers (Bearer) - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://swagger.io/docs/specification/authentication/&#34;&gt;Authentication and Authorization - swagger.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞抓蟲: DateTime.ToString() 之我們不一樣 &amp; CultureInfo 文化特性小筆記</title>
      <link>https://igouist.github.io/post/2021/10/csharp-datatime-tostring-cultureinfo/</link>
      <pubDate>Mon, 04 Oct 2021 22:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/10/csharp-datatime-tostring-cultureinfo/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/EMdGkwr.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;337&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;事發緣由&#34;&gt;事發緣由&lt;/h2&gt;
&lt;p&gt;咱們內部套件中有個方法，會將各個參數組合為 QueryString 去打指定的 Api。就是這麼稀鬆平常的場景，神奇的事情就發生了。&lt;/p&gt;
&lt;p&gt;同樣的套件、同樣的語法，在團隊中兩個人的電腦上安裝執行，卻是一個成功一個失敗。&lt;/p&gt;
&lt;p&gt;原來該方法的參數中，包含一欄型別為 DateTime 的資料，並且會把該欄位的值拿來 ToString() 再做為參數傳遞給目標 Api。&lt;/p&gt;
&lt;p&gt;而呼叫失敗的人就是在這個 &lt;code&gt;DateTime.ToString()&lt;/code&gt; 的過程中&lt;strong&gt;產生了中文字&lt;/strong&gt;，使得目標 Api 接到參數後，無法將中文字轉換回 DateTime 而發生了錯誤。&lt;/p&gt;
&lt;p&gt;問題就浮現了：&lt;strong&gt;同一行 DateTime.ToString() 在不同電腦執行的結果竟然不一樣？！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;為了讓我們更快了解狀況，現在就簡單地使用 Linqpad 進行測試：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    DateTime.Now.ToString().Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;首先，在我的 Windows 時間設定中，完整時間的格式為 &lt;code&gt;09:40:07&lt;/code&gt; ，也就是 24 小時制。&lt;/p&gt;
&lt;p&gt;現在讓我們先執行上面這段語法看看：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 2021/10/04 21:00:00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接著讓我們變更時間格式看看。&lt;/p&gt;
&lt;p&gt;以我的 Win10 為例，在 Windows 工具列，也就是畫面的右下角右鍵，選擇 &lt;code&gt;調整日期時間 → 日期時間格式設定 → 變更資料格式&lt;/code&gt;，將時間格式變更為 &lt;code&gt;上午 09:40:07&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;重新啟動 Linqpad 再執行如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 2021/10/04 下午 09:00:00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到 &lt;code&gt;下午&lt;/code&gt; 兩個字就蹦出來了！&lt;/p&gt;
&lt;p&gt;之所以會有這樣的差異，是因為 &lt;code&gt;DateTime.ToString()&lt;/code&gt; 預設轉換的目標格式會是抓取目前執行緒的&lt;strong&gt;文化特性&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;文化特性&#34;&gt;文化特性&lt;/h2&gt;
&lt;p&gt;在 CSharp 中有個專門負責處理文化特性、地區設定等在地化處理的類別：&lt;strong&gt;CultureInfo&lt;/strong&gt;。舉凡國家地區的資訊、時間日期的格式、字串的排序方式等等，都是它的工作。&lt;/p&gt;
&lt;p&gt;要取得當前的文化特性，我們可以使用 &lt;code&gt;CultureInfo.CurrentCulture&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CultureInfo.CurrentCulture.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// zh-TW&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到我們目前的文化特性是 &lt;code&gt;zh-TW&lt;/code&gt;，這個格式叫做 &lt;a href=&#34;https://zh.wikipedia.org/wiki/IETF%E8%AA%9E%E8%A8%80%E6%A8%99%E7%B1%A4&#34;&gt;IETF 語言標籤&lt;/a&gt;，其中 &lt;code&gt;zh&lt;/code&gt; 是指語言，&lt;code&gt;TW&lt;/code&gt; 則是地區。例如說 &lt;code&gt;en-US&lt;/code&gt; 就是「英語，美國」。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：關於語言標籤，有需要查詢的朋友可以翻看看 &lt;a href=&#34;http://www.i18nguy.com/unicode/language-identifiers.html&#34;&gt;i18n 的 RFC 3066 表&lt;/a&gt;。
&lt;br/&gt;此外，我們平常用的語言標籤還會有一些子標籤之類的，例如 &lt;code&gt;zh-Hant-HK&lt;/code&gt;，可以參見 &lt;a href=&#34;https://www.w3.org/International/articles/language-tags/&#34;&gt;Language tags in HTML and XML (w3.org)&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：除了 &lt;code&gt;CurrentCulture&lt;/code&gt; 以外，CultureInfo 還有另一組文化特性的設定：&lt;code&gt;CurrentUICulture&lt;/code&gt;。從名稱上可以看得出來，前者是用來預設系統的文化設定，例如數值、排序等等；而加上 &lt;code&gt;UI&lt;/code&gt; 的後者，則是用來預設使用者介面要顯示什麼語言。&lt;/p&gt;
&lt;p&gt;當然，大多數時候這兩個文化特性會是一樣的，不過如果想要國際化，弄個使用者介面的多國語言版本，就需要特別注意一下囉！&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/329033/what-is-the-difference-between-currentculture-and-currentuiculture-properties-of&#34;&gt;What is the difference between CurrentCulture and CurrentUICulture properties of CultureInfo in .NET? - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2015/11/26/multiple_language_culture&#34;&gt;ASP.NET 多國語系 - 了解文化特性 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.azurewebsites.net/shadow/2018/09/03/172157&#34;&gt;ASP.net Core 多國語系 Part 1 初探篇 | 高級打字員的技術雲 - 點部落 (dotblogs.azurewebsites.net)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;接著讓我們來看看裡面包了哪些重要東西吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UFOiAjM.webp&#34;
  alt=&#34;Image&#34;width=&#34;664&#34; height=&#34;399&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;datetimeformat&#34;&gt;DateTimeFormat&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DateTimeFormat&lt;/code&gt; 是用來放跟 &lt;code&gt;DateTime&lt;/code&gt; 相關的設定值的，其中包含了該文化的日期時間的顯示格式。&lt;/p&gt;
&lt;p&gt;我們同樣用 Linqpad 看一下裡面有些什麼：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CultureInfo.CurrentCulture.DateTimeFormat.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/P843qZ0.webp&#34;
  alt=&#34;Image&#34;width=&#34;528&#34; height=&#34;981&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到裡面包含了一年有哪些月份和名稱、一週有哪些日子和名稱，以及一堆時間格式。&lt;/p&gt;
&lt;p&gt;那一堆時間格式主要是對照 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/standard-date-and-time-format-strings&#34;&gt;標準日期和時間格式字串&lt;/a&gt; 的。&lt;/p&gt;
&lt;p&gt;例如 ShortDatePattern 就是對應到文件中的簡短日期模式（&lt;code&gt;&amp;quot;d&amp;quot;&lt;/code&gt;），也就是說，當我們 &lt;code&gt;ToString&lt;/code&gt; 的時候，如果使用 &amp;ldquo;d&amp;rdquo; 的話，就會根據文化特性顯示該文化的「簡短日期」以此類推。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於這部分的欄位內容，可以參照 Vito 大大的 &lt;a href=&#34;http://vito-note.blogspot.com/2012/03/blog-post_28.html#%E9%A1%AF%E7%A4%BA%E8%88%87%E6%96%87%E5%8C%96%E7%89%B9%E6%80%A7%E7%9B%B8%E9%97%9C%E7%9A%84%E8%B3%87%E8%A8%8A&#34;&gt;使用文化特性&lt;/a&gt; 這篇文章，裡面的整理已經相當詳細，供各位參考。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：我們常常會看到 &lt;code&gt;ToString(&amp;quot;yyyy-MM-dd HH:mm:ss&amp;quot;)&lt;/code&gt; 這種用法，但要小心這邊的 &lt;strong&gt;&lt;code&gt;-&lt;/code&gt; 和 &lt;code&gt;:&lt;/code&gt; 其實是用來標示日期和時間的分隔符號&lt;/strong&gt;，並不是真正的 &lt;code&gt;-&lt;/code&gt; 和 &lt;code&gt;:&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以 &lt;code&gt;:&lt;/code&gt; 在 format 的時候，會被替換為 &lt;code&gt;TimeSeparator&lt;/code&gt;；&lt;br/&gt;而 &lt;code&gt;/&lt;/code&gt; 同樣也會使用 &lt;code&gt;DateSeparator&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;也就是說，如果你使用的文化特性 &lt;code&gt;TimeSeparator&lt;/code&gt; 是 &lt;code&gt;#&lt;/code&gt; 的話，&lt;code&gt;HH:mm:ss&lt;/code&gt; 在 &lt;code&gt;21:07:00&lt;/code&gt; 出來的結果將會是 &lt;code&gt;21#07#00&lt;/code&gt;。如果發現 ToString 出來的結果怪怪的，可以優先檢查文化特性的這兩個分隔符號有沒有問題。&lt;/p&gt;
&lt;p&gt;相關的說明請參見 &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings#date-and-time-separator-specifiers&#34;&gt;Date and time separator specifiers&lt;/a&gt; 這一小節。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在讓我們用 &lt;code&gt;Thread.CurrentThread.CurrentCulture&lt;/code&gt; 來更改當前執行緒的時間，並用完整日期時間模式（&lt;code&gt;&amp;quot;F&amp;quot;&lt;/code&gt;）來測試看看：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; date = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DateTime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2006&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Thread.CurrentThread.CurrentCulture = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-tw&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;F&amp;#34;&lt;/span&gt;).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 2006年1月2日 03:04:05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Thread.CurrentThread.CurrentCulture = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-cn&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;F&amp;#34;&lt;/span&gt;).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 2006年1月2日 3:04:05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Thread.CurrentThread.CurrentCulture = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;en-us&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;F&amp;#34;&lt;/span&gt;).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// Monday, January 2, 2006 3:04:05 AM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當然，在 ToString 的時候直接扔文化特性進去也是可以的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; date = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DateTime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2006&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date.ToString(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-tw&amp;#34;&lt;/span&gt;)).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 2006/1/2 03:04:05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date.ToString(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-cn&amp;#34;&lt;/span&gt;)).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 2006/1/2 3:04:05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date.ToString(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;en-us&amp;#34;&lt;/span&gt;)).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 1/2/2006 3:04:05 AM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而本篇起因的翻船事件，我們可以試著觀察一下 &lt;code&gt;CultureInfo&lt;/code&gt; 的預設曆法，也就是 &lt;code&gt;Calendar&lt;/code&gt; 的內容：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	CultureInfo.CurrentCulture.Calendar.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	CultureInfo.CurrentUICulture.Calendar.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 完整時間格式: 15:04:05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// MinSupportedDateTime 0001/1/1 上午 12:00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// MaxSupportedDateTime 9999/12/31 下午 11:59:59&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 完整時間格式: 下午 03:04:05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// MinSupportedDateTime 0001/1/1 00:00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// MaxSupportedDateTime 9999/12/31 23:59:59&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以明確看到格式的變更，兇手就在我們之中！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;其實文檔已經明確地說了：使用者可以透過主控台的『地區及語言選項』部分，選擇覆寫與 Windows 目前文化特性相關聯的一些值。
（&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.globalization.cultureinfo.calendar&#34;&gt;CultureInfo.Calendar 屬性 (System.Globalization) | Microsoft Docs&lt;/a&gt;）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;當然，我們也可以調整 Calendar 來把曆法換掉。例如說當我們要做西元年轉民國年的時候，就可以把 Calendar 指定為 TaiwanCalendar 來處理。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這部分請參照：&lt;a href=&#34;https://kevintsengtw.blogspot.com/2014/06/c.html&#34;&gt;mrkt 的程式學習筆記: 基本題 - C# 西元年轉換取得民國年格式字串&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;有了這些日期時間的格式，我們就可以用它們來處理一些日常的時間處理囉～例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/10/17/141620&#34;&gt;食譜好菜 DateTime 具有文化特性的格式化及時區的轉換 | 軟體主廚的程式料理廚房 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/echo/2016/10/16/dotnet_dayofweek_displaybycultureinfo&#34;&gt;【.NET】利用 CultureInfo 取得各語系星期顯示名稱 | 暴走的程式碼… - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;numberformat&#34;&gt;NumberFormat&lt;/h3&gt;
&lt;p&gt;除了日期格式以外，數值也是各個文化常常不同的部份。在 &lt;code&gt;CultureInfo&lt;/code&gt; 中的 &lt;code&gt;NumberFormat&lt;/code&gt; 就是用來處理數值相關的格式。&lt;/p&gt;
&lt;p&gt;我們同樣用 Linqpad 看一下裡面有些什麼：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CultureInfo.CurrentCulture.NumberFormat.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yqswJrJ.webp&#34;
  alt=&#34;Image&#34;width=&#34;333&#34; height=&#34;976&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見裡面包含了 &lt;code&gt;CurrencySymbol&lt;/code&gt; 貨幣符號、&lt;code&gt;PercentSymbol&lt;/code&gt; 百分比符號、&lt;code&gt;CurrencyNegativePattern&lt;/code&gt; 貨幣負數格式 等等數值相關的設定。&lt;/p&gt;
&lt;p&gt;現在讓我們使用 &lt;code&gt;ToString(&amp;quot;C&amp;quot;)&lt;/code&gt; 來指定轉換為貨幣格式，並且丟不同的文化特性進去看看吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; price = -&lt;span style=&#34;color:#ae81ff&#34;&gt;49.99&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;price.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-tw&amp;#34;&lt;/span&gt;)).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// -NT$49.99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;price.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-cn&amp;#34;&lt;/span&gt;)).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// ¥-49.99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;price.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;en-us&amp;#34;&lt;/span&gt;)).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// ($49.99)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看見整個格式都不一樣了呢。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：&lt;code&gt;CurrencyNegativePattern&lt;/code&gt; 這類的格式只會存數字編號，例如 0 對應到 ($n) 之類的。
各編號對應的格式可以參見 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.globalization.numberformatinfo.currencynegativepattern&#34;&gt;NumberFormatInfo.CurrencyNegativePattern 屬性 (System.Globalization) | Microsoft Docs&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;invariantculture&#34;&gt;InvariantCulture&lt;/h3&gt;
&lt;p&gt;那麼，當我們想要&lt;strong&gt;統一格式&lt;/strong&gt;的時候呢？例如說我們在世界各地都有服務，這些客戶端的資訊會集中傳回主伺服器。要是直接 &lt;code&gt;ToString&lt;/code&gt; 那鐵定是每個地方回來的時間、貨幣和數值格式都不一樣的。該怎麼辦呢？總不能發個字串下去叫大家寫死吧囧？&lt;/p&gt;
&lt;p&gt;這時候就可以使用 &lt;code&gt;InvariantCulture&lt;/code&gt; 啦！&lt;/p&gt;
&lt;p&gt;讓我們看看文檔是怎麼說明的：「取得與文化特性無關的 (不變的) CultureInfo 物件」、「&lt;strong&gt;不區分文化特性的文化特性不區分文化特性&lt;/strong&gt;」（也太饒舌）&lt;/p&gt;
&lt;p&gt;也就是說，只要使用約定好一起使用這組不變的文化特性，就可以保證大家的格式都是同一套囉。&lt;/p&gt;
&lt;p&gt;使用的時候可以 &lt;code&gt;CultureInfo.InvariantCulture&lt;/code&gt; 或是直接 &lt;code&gt;new CultureInfo(&amp;quot;&amp;quot;)&lt;/code&gt; 就可以取得這組不變的文化特性。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; culinfo = CultureInfo.InvariantCulture;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; date = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DateTime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2006&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date.ToString(culinfo).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 01/02/2006 03:04:05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; price = -&lt;span style=&#34;color:#ae81ff&#34;&gt;49.99&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;price.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;C&amp;#34;&lt;/span&gt;, culinfo).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// (¤49.99)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;del&gt;不過如果覺得 月/日/年 的格式很鳥的話，其實大家約好挑一組文化特性就好了齁？&lt;/del&gt;&lt;/p&gt;
&lt;h2 id=&#34;後日談&#34;&gt;後日談&lt;/h2&gt;
&lt;p&gt;最後交代一下篇頭的問題怎麼解決的：&lt;/p&gt;
&lt;p&gt;直接把該套件打開然後把 &lt;code&gt;.ToString()&lt;/code&gt; 加上 &lt;code&gt;&amp;quot;yyyy/MM/dd&amp;quot;&lt;/code&gt; 打完收工囧。&lt;/p&gt;
&lt;p&gt;就是這麼簡單。爽發一篇廢文，筆記筆記。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2022.05.06 補充：&lt;/p&gt;
&lt;p&gt;朋友在將 Asp.net Framework 的服務佈到多台 Windows Server 上時，也遇到了時區設定不一致的問題。&lt;/p&gt;
&lt;p&gt;經過一番排查，最後決定&lt;strong&gt;在 Web.config 中的 System.web 設定 Globalization 指定服務採用的時區&lt;/strong&gt;來解決這個問題。&lt;/p&gt;
&lt;p&gt;這邊也提供相關的資訊給遇到同樣問題的朋友們參考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2015/11/26/multiple_language_culture&#34;&gt;多國語系 - 了解文化特性 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;請參閱：「在 Web.config 設定文化特性」一節&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/troubleshoot/developer/webapps/aspnet/development/set-current-culture&#34;&gt;在 ASP.NET 中以程式設計方式設定目前的文化環境 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://vito-note.blogspot.com/2012/03/blog-post_28.html&#34;&gt;VITO の 學習筆記: 使用文化特性 (vito-note.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2015/11/26/multiple_language_culture&#34;&gt;ASP.NET 多國語系 - 了解文化特性 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/10/17/141620&#34;&gt;食譜好菜 DateTime 具有文化特性的格式化及時區的轉換 | 軟體主廚的程式料理廚房 - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/echo/2016/10/16/dotnet_dayofweek_displaybycultureinfo&#34;&gt;【.NET】利用 CultureInfo 取得各語系星期顯示名稱 | 暴走的程式碼… - 點部落 (dotblogs.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.azurewebsites.net/shadow/2018/09/03/172157&#34;&gt;ASP.net Core 多國語系 Part 1 初探篇 | 高級打字員的技術雲 - 點部落 (dotblogs.azurewebsites.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/GreenLeaves/p/6757917.html&#34;&gt;C# CultureInfo 中常用的 InvariantCulture - 郑小超 - 博客园 (cnblogs.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.globalization.cultureinfo&#34;&gt;CultureInfo 類別 (System.Globalization) | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.globalization.cultureinfo.currentculture&#34;&gt;CultureInfo.CurrentCulture 屬性 (System.Globalization) | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/329033/what-is-the-difference-between-currentculture-and-currentuiculture-properties-of&#34;&gt;What is the difference between CurrentCulture and CurrentUICulture properties of CultureInfo in .NET? - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/9760237/what-does-cultureinfo-invariantculture-mean&#34;&gt;.net - What does CultureInfo.InvariantCulture mean? - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧</title>
      <link>https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture/</link>
      <pubDate>Sun, 03 Oct 2021 14:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/S72H7sA.webp&#34;
  alt=&#34;img&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是俺整理公司新訓內容的第五篇文章，目標是&lt;strong&gt;使用三層式架構 (3-Layer Architecture) 來切分服務的關注點和職責&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#什麼是分層分層可以吃嗎&#34;&gt;什麼是分層？分層可以吃嗎？&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#三層式架構&#34;&gt;三層式架構&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#關於-dto&#34;&gt;關於 DTO&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#為什麼我們需要分層架構&#34;&gt;為什麼我們需要分層架構？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#本節的參考資料&#34;&gt;本節的參考資料&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#實作&#34;&gt;實作&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#實作範例的架構與前言&#34;&gt;實作範例的架構與前言&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#大致上的步驟&#34;&gt;大致上的步驟&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#範例專案背景&#34;&gt;範例專案背景&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#建立分層&#34;&gt;建立分層&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#加入參考&#34;&gt;加入參考&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#建立介面與-dto&#34;&gt;建立介面與 DTO&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#實作-repository&#34;&gt;實作 Repository&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#實作-service&#34;&gt;實作 Service&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#實作-controller&#34;&gt;實作 Controller&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#自問自答心得篇&#34;&gt;自問自答心得篇&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#關於重複建立-dto-的問題&#34;&gt;關於重複建立 DTO 的問題&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#三層式架構跟-mvc-一樣嗎&#34;&gt;三層式架構跟 MVC 一樣嗎？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#service-可以依賴-service-嗎&#34;&gt;Service 可以依賴 Service 嗎？&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#本系列文章&#34;&gt;本系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;什麼是分層分層可以吃嗎&#34;&gt;什麼是分層？分層可以吃嗎？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;天地混沌如雞子，商業邏輯生其中。&lt;/p&gt;
&lt;p&gt;萬八千歲，天地開闢。表現層為天。資料層為地。商業邏輯層在其中……&lt;/p&gt;
&lt;p&gt;　　　　－－民明書坊《盤古與他的CRUD之旅》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;根據民明書坊的文獻記載，我們常聽到的「天地玄黃，宇宙洪荒」云云，其實指的就是上古時期的開發狀況。當時世界還是一片混沌，所有的程式碼都混雜成一坨，不是所有東西寫在一起你儂我儂，一言不合就三千行；就是依賴關係交錯複雜，改了北極壞南極。&lt;/p&gt;
&lt;p&gt;要說有多亂呢，大概就算前人嘗試引入了 MVC，也只是改成把所有程式都塞在 Controller 而已，其絕望程度可見一斑。&lt;/p&gt;
&lt;p&gt;這時候隔壁課的老盤調過來接刀，一看不得了，便決定先對這屎山整頓一番。他大喝一聲，那些靠近使用者的便上浮起來化作了天，親近資料庫的便沉澱下去變成了地，而所有的商業邏輯就連接著兩者，支撐起了整個專案。這也就是分層架構的由來。&lt;/p&gt;
&lt;h3 id=&#34;三層式架構&#34;&gt;三層式架構&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;分層架構是運用最為廣泛的架構模式，幾乎每個軟體系統都需要通過層（Layer）來隔離不同的關注點（Concern Point），以此應對不同需求的變化，使得這種變化可以獨立進行；此外，分層架構模式還是隔離業務複雜度與技術複雜度的利器。 －－ &lt;a href=&#34;https://raychiutw.github.io/2019/%E9%9A%A8%E6%89%8B-Design-Pattern-2-%E8%BB%9F%E9%AB%94%E5%88%86%E5%B1%A4%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-Software-Layered-Architecture-Pattern/&#34;&gt;Ray&amp;rsquo;s Notes&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;一般來說，最常見的分層架構就是&lt;strong&gt;三層式架構&lt;/strong&gt;了。&lt;/p&gt;
&lt;p&gt;三層式架構顧名思義就是把應用程式分成三層，通常會分成「&lt;strong&gt;展示層、商業邏輯層、資料存取層&lt;/strong&gt;」。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/RxSrWJm.webp&#34;
  alt=&#34;分層架構01&#34;width=&#34;779&#34; height=&#34;330&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在讓我們認識一些他們的分工吧！&lt;/p&gt;
&lt;p&gt;三層式架構的常見分層有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;展示層（Presentation Layer）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;咱們軟體的門面。&lt;strong&gt;負責搞定需要跟外部使用者互動的部份&lt;/strong&gt;，例如接收使用者的請求、路由的控制、呼叫的流程控制等等&lt;/li&gt;
&lt;li&gt;日常工作就是確實地接收使用者的請求，然後叫商業邏輯層去處理，最後把商業邏輯層弄好的東西奉上給使用者&lt;/li&gt;
&lt;li&gt;大多時候的開發會在 &lt;code&gt;Controller&lt;/code&gt; 進行，例如 &lt;code&gt;ProductController&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;&lt;strong&gt;商業邏輯層（Business Layer）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;咱們軟體的核心。&lt;strong&gt;負責處理商業邏輯&lt;/strong&gt;，也就是商業規則和相關的邏輯處理都在這裡進行&lt;/li&gt;
&lt;li&gt;日常工作就是接收展示層的呼叫、和資料存取層拿資料。在這個來往的過程中將資料內容進行商業邏輯的處理&lt;/li&gt;
&lt;li&gt;常見的後綴有 &lt;code&gt;BLL&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt; 等等，例如 &lt;code&gt;ProductService&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;&lt;strong&gt;資料存取層（Data Layer）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;顧名思義，就是&lt;strong&gt;負責存取資料的相關操作&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;日常工作就是根據商業邏輯層的要求，去資料庫存取資料&lt;/li&gt;
&lt;li&gt;常見的後綴有 &lt;code&gt;DAL&lt;/code&gt;, &lt;code&gt;Repository&lt;/code&gt; 等等，例如 &lt;code&gt;ProductRepository&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外一些比較中大型的架構會把共用的部份抽出來，就會多一層：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;共用層（Common Layer）&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;負責放一些&lt;strong&gt;各層之間會共用到的工具&lt;/strong&gt;。例如擴充方法、列舉等等&lt;/li&gt;
&lt;li&gt;因為是用來放各種工具的，所以需要分離出一層，來讓各層都可以使用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用最常見的餐廳來比喻的話，資料庫大概就像是存放食材的冰箱、其他服務的ＡＰＩ就像是供應商之類的，各層的分工就會像這樣：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;資料層：負責採買食材、去冰箱拿食材等等&lt;/li&gt;
&lt;li&gt;業務層：根據各式各樣的食譜把食材變成料理&lt;/li&gt;
&lt;li&gt;展示層：負責櫃台的點餐，將做好的料理呈上給客人&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;對這種過於模糊的比喻不太能理解的朋友，假設今天某網站的管理後台有個查詢某客戶的請求，可能會是這樣子：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;展示層 收到使用者查詢客戶的請求
&lt;ul&gt;
&lt;li&gt;ex: &lt;code&gt;GET Cust/1&lt;/code&gt; -&amp;gt; &lt;code&gt;CustController.Get(int custId)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;展示層 檢查是否登入和權限 ex: &lt;code&gt;[Authorize(Roles = UserRole.Admin)]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;展示層 呼叫商業邏輯層查詢客戶 ex: &lt;code&gt;CustService.Get(int custId)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;商業邏輯層 呼叫資料存取層查詢客戶 ex: &lt;code&gt;CustRepository.Get(int custId)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;資料存取層 從 &lt;code&gt;Cust&lt;/code&gt; 資料表取回資料&lt;/li&gt;
&lt;li&gt;商業邏輯層 繼續執行，發現有「查詢客戶時需同時列出該客戶五筆最近訂單簡介」的規則
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;別問為什麼，問就是 Feature&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;商業邏輯層 呼叫資料存取層
&lt;ul&gt;
&lt;li&gt;ex: &lt;code&gt;OrderRepository.GetLast(int custId, int count = 5)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;資料存取層 從 &lt;code&gt;Order&lt;/code&gt; 資料表取回資料&lt;/li&gt;
&lt;li&gt;商業邏輯層 根據規則組裝資料，回傳給展示層&lt;/li&gt;
&lt;li&gt;展示層 取得商業邏輯層回覆的資料，回傳給使用者&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;大概就是這種感覺。&lt;/p&gt;
&lt;img src=&#34;https://i.imgur.com/GF7cF9f.jpg&#34; alt=&#34;分層架構02&#34; style=&#34;zoom:33%;&#34; /&gt;
&lt;p&gt;當然，這邊還是要再提醒一下：分層架構並不是只有三層式架構，根據需求也可能會再增加或是減少。同樣地，&lt;strong&gt;分層架構只是一種「分工的概念」&lt;/strong&gt;，並不是只限於軟體，也不一定得就是這三層。&lt;/p&gt;
&lt;p&gt;曾經也有聽過「瀏覽器（展示層）、伺服器（商業邏輯層）、資料庫（資料存取層）」的說法，也看過遊戲是用「整體策略、基本操作」等動作規模去分層的，因此只需要有「把工作依據職責切分到不同層」的概念就可以了，切莫要走火入魔。&lt;/p&gt;
&lt;h3 id=&#34;關於-dto&#34;&gt;關於 DTO&lt;/h3&gt;
&lt;p&gt;我們回到上面的例子，可以注意到各層之間必須要頻繁地溝通、傳遞資訊。因此我們就會&lt;strong&gt;需要一些物件來幫忙在各層之間傳遞這些資料&lt;/strong&gt;，這些只有資料欄位、沒有任何方法的物件就是 &lt;strong&gt;DTO（Data Transfer Object, 資料傳輸物件）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在分層架構裡面使用 DTO 是絕對必要的，除了層與層之間需要讓資料傳遞之外，也能帶來幾個好處，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;封裝過多的參數
&lt;ul&gt;
&lt;li&gt;針對參數過多的方法，我們可以收納成 Dto 來隱藏複雜性&lt;/li&gt;
&lt;li&gt;封裝前 &lt;code&gt;GetProductList(int custLevel, string custName, int.....)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;封裝後 &lt;code&gt;GetProductList(GetProductParameter parameter)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;減少層與層之間的耦合
&lt;ul&gt;
&lt;li&gt;當有修改欄位的時候，有時只需變動 DTO 就可以了&lt;/li&gt;
&lt;li&gt;各層之間的溝通使用 Dto 傳輸，可以減少各層直接彼此影響的耦合狀況&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這裡其實就是&lt;strong&gt;將「資料」和「方法」切割開來封裝&lt;/strong&gt;的概念。如果切分得不錯的話，在呼叫的過程就會像是&lt;strong&gt;工廠流水線&lt;/strong&gt;的感覺，資料隨著運輸帶到達每個工作區，然後工作區對資料進行處理之後，再送上運輸帶前往下個工作區，經歷了壯闊的旅程（？）之後最後終於到達使用者手上。&lt;/p&gt;
&lt;p&gt;不過相對的，每層之間都用 DTO 去通訊、過多的參數封裝成 DTO 等等，都會增加整個系統中的類別數量，有時候甚至會多到畫面上列不下的程度（我的 DTO 就像宇宙一樣廣闊！區區方案總管休想裝得下我！），反而造成管理和修改上的困難。&lt;/p&gt;
&lt;p&gt;因此 DTO 的應用場景，例如說是每一層都需要獨立的 DTO 嗎？或是使用同一個 DTO 來操作呢？會不會共用了參數反而造成耦合呢？都要到開發的時候來作取捨。&lt;/p&gt;
&lt;p&gt;關於上面這段重複建立 DTO 的問題，我們在最後的 &lt;a href=&#34;#%E9%97%9C%E6%96%BC%E9%87%8D%E8%A4%87%E5%BB%BA%E7%AB%8B-dto-%E7%9A%84%E5%95%8F%E9%A1%8C&#34;&gt;QA 階段&lt;/a&gt; 再稍微聊一下吧，先讓我們把鏡頭轉回分層架構。&lt;/p&gt;
&lt;h3 id=&#34;為什麼我們需要分層架構&#34;&gt;為什麼我們需要分層架構？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;專案分層架構其目的就是為了要職責確立、關注點分離，讓不同的方法或類別去做該做的事情而且只專注於這些方法、類別的職責上 －－ &lt;a href=&#34;https://kevintsengtw.blogspot.com/2012/10/aspnet-mvc-part1.html&#34;&gt;mrkt&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;當我們想知道為什麼要使用分層架構，最快的方式就是了解一下採用分層架構的諸多好處。且讓我數給你聽：&lt;/p&gt;
&lt;h4 id=&#34;更符合單一職責關注點分離&#34;&gt;更符合單一職責、關注點分離&lt;/h4&gt;
&lt;p&gt;也就是我們在單一職責原則篇曾提到的：「把工作交給負責該職責的類別去做，自己只需要關注在自己正在處理的職責即可」。如果一來就可以封裝出邊界、減少彼此耦合影響的機會，也可以減少閱讀大量不相關的程式碼。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於單一職責的介紹，和我們著重職責所帶來的好處等等，&lt;br/&gt;可以參閱之前的：&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle/&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h4 id=&#34;快速鎖定目標縮小範圍&#34;&gt;快速鎖定目標、縮小範圍&lt;/h4&gt;
&lt;p&gt;因為關注點分離成多個層（Layer）了，當物件的設計有符合職責的話，在開發或除錯時，甚至能像查詢表格一樣迅速！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CZ0S51s.webp&#34;
  alt=&#34;分層架構04&#34;width=&#34;2606&#34; height=&#34;863&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當分層和類別的定義都合乎職責的時候，在接收到需求的同時也就能抓出目標範圍在哪了，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;這次針對某功能的一些判斷邏輯有變動 =&amp;gt; OK，先從 Service 開始看&lt;/li&gt;
&lt;li&gt;這個資料表的欄位名字被換掉了 =&amp;gt; OK，往 Repository 前進&lt;/li&gt;
&lt;li&gt;這個路由可以幫我調整一下嗎 =&amp;gt; OK，Controller 改起來&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原先你可能要先閱讀一坨又臭又長的程式碼，經歷一場垃圾探險記、花一堆時間從裡面找到你要改的地方才能開工。有明確的架構之後，就可以大幅地提升精準度，直接往相應職責的地方去就對了。&lt;/p&gt;
&lt;p&gt;因此，乾淨、分工明確的架構可以給你大方向的指引，替你省下許多垃圾時間，實在是舒服許多。&lt;/p&gt;
&lt;h4 id=&#34;適合多人合作減少碰撞率&#34;&gt;適合多人合作、減少碰撞率&lt;/h4&gt;
&lt;p&gt;因為範圍縮小了，多人合作的時候就可以靈活地去調度、搭配，提升開發時的效率。&lt;/p&gt;
&lt;p&gt;除了平常的你負責Ａ功能、我負責Ｂ功能以外，也可以嘗試讓比較熟悉資料庫的朋友處理資料存取層、負責和 API 使用端商談規格的朋友先在展示層開好 API 接口等等。&lt;/p&gt;
&lt;p&gt;如此就有了可以根據狀況從分層或是功能等方向去分工的彈性。並且這樣的分工也能減少碰撞率 ……至少會讓 Git 的衝突少一點囧。&lt;/p&gt;
&lt;h4 id=&#34;增加程式碼的複用性&#34;&gt;增加程式碼的複用性&lt;/h4&gt;
&lt;p&gt;最後也是最有感覺的就是增加程式碼的複用性了。在商業邏輯中可能很多地方都會用到其他來源的資料來加工組合，例如查詢客戶的時候要一併列出訂單、推薦商品的時候要附上目前的優惠活動等等。&lt;/p&gt;
&lt;p&gt;這時候如果分層明確，一些像是資料存取層中的「查詢訂單」這種簡單又符合單一職責的方法，我們就可以加以取用，靈活組合出目前的需求。&lt;/p&gt;
&lt;p&gt;比起到處都把同一段撈資料的 Code 複製貼上複製貼上，然後要修改的時候整個遍地開花的狀況，能夠重複使用實在是舒服許多。&lt;/p&gt;
&lt;p&gt;到目前為止這應該是我最有感覺的一項，當你需要撈某個資料出來，發現隊友或是之前的自己已經寫好一個乾淨可用好擴充的 Function 在那邊讓你直接呼叫，那感覺真的是一個爽呀！&lt;/p&gt;
&lt;h4 id=&#34;提供抽換的靈活度&#34;&gt;提供抽換的靈活度&lt;/h4&gt;
&lt;p&gt;這一項比較像是前面各項產生的結果。由於我們根據了不同職責來拆分出各層，因此當我們面臨問題的時候是可以整層進行抽換的。&lt;/p&gt;
&lt;p&gt;例如說當我們除了原先的網頁以外，還要提供 API 服務，那麼展示層即使抽換成了 Web API 專案，對整體的架構仍不會有影響，還是能保持同一套商業邏輯。&lt;/p&gt;
&lt;p&gt;又或者是說今天這個專案的資料來源要從 MySQL 換到 MSSQL 之類的，我們就可以把資料存取層替換掉，而不影響商業邏輯和展示層的運作。&lt;/p&gt;
&lt;h4 id=&#34;享受以上優點的前提&#34;&gt;享受以上優點的前提&lt;/h4&gt;
&lt;p&gt;但要真的享受到上述的各項優點，還是需要達成一定的條件的，並不是說直接切三塊就「完！」這樣。例如說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;物件的設計需要遵守 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid/&#34;&gt;SOLID 原則&lt;/a&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;包括物件符合單一職責、各個物件之間使用介面來減少耦合等等&lt;/li&gt;
&lt;li&gt;其實這一項還蠻直覺的：畢竟如果你的方法完全不管單一職責，動不動就塞個上百行的 SQL 之類的，當然就很難重複利用了嘛&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要降低各層之間的依賴&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;為了解除層與層之間的直接依賴，因此會需要使用介面和依賴注入等解耦手段&lt;/li&gt;
&lt;li&gt;只有將各層之間的耦合降低，才能實現可替換、關注點分離等效果&lt;/li&gt;
&lt;li&gt;關於依賴注入的概念，可以參照 &lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/&#34;&gt;依賴注入章節&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;註：本篇的範例會先建立簡單的分層，下一篇才會進入依賴注入的實作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可能會有額外的開發成本&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;即使是再簡單的功能，例如單純的查詢，也必須要貫穿每一層。自然就增加了開發成本
&lt;ul&gt;
&lt;li&gt;有時候只是為了要多提供一個欄位給使用者，就會變成從上到下的每一層都需要修改&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不過我個人覺得比起東西都塞在一起然後動輒上千行還改東壞西的，我是很能接受這些成本啦囧&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;本節的參考資料&#34;&gt;本節的參考資料&lt;/h3&gt;
&lt;p&gt;由於接下來我們就要進入實作了，因此在這邊先放上分層架構概念的參考資料，提供給想更了解三層式架構觀念的朋友們。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2012/10/aspnet-mvc-part1.html&#34;&gt;mrkt 的程式學習筆記: ASP.NET MVC 專案分層架構 Part.1 初學者的起手式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://raychiutw.github.io/2019/%E9%9A%A8%E6%89%8B-Design-Pattern-2-%E8%BB%9F%E9%AB%94%E5%88%86%E5%B1%A4%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-Software-Layered-Architecture-Pattern/&#34;&gt;隨手 Design Pattern (2) - 軟體分層設計模式 (Software Layered Architecture Pattern) | Ray&amp;rsquo;s Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.johnwu.cc/article/software-layered-architecture-pattern.html&#34;&gt;軟體分層架構模式 | John Wu&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://shunnien.github.io/2017/07/29/3-tier-and-mvc-introduction/&#34;&gt;三層結構與 Asp.Net MVC 的簡介 | ShunNien&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/%E4%B8%89%E5%B1%A4%E5%BC%8F%E6%9E%B6%E6%A7%8B/&#34;&gt;三層式架構 | Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@steph.c/%E4%B8%89%E5%B1%A4%E6%9E%B6%E6%A7%8B%E6%98%AF%E4%BB%80%E9%BA%BC-%E6%88%91%E5%8F%AA%E7%9F%A5%E9%81%93%E4%B8%89%E5%B1%A4%E8%82%89-efe542c38aaf&#34;&gt;MVC 三層架構 是什麼? 我只知道三層肉. 三層架構 (3-Tier Architecture) 是哪三層 ? | by Steph Dev 史帝夫和戴夫&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;實作&#34;&gt;實作&lt;/h2&gt;
&lt;h3 id=&#34;實作範例的架構與前言&#34;&gt;實作範例的架構與前言&lt;/h3&gt;
&lt;p&gt;在這次的範例中，我會採用在公司時的切割標準：各層之間不同方向的通訊都有獨立的 DTO 來負責。&lt;/p&gt;
&lt;p&gt;當然&lt;strong&gt;每一層和每個 DTO 的名稱都是很彈性的&lt;/strong&gt;，例如 &lt;a href=&#34;https://raychiutw.github.io/2019/%E9%9A%A8%E6%89%8B-Design-Pattern-2-%E8%BB%9F%E9%AB%94%E5%88%86%E5%B1%A4%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-Software-Layered-Architecture-Pattern/&#34;&gt;Ray&lt;/a&gt; 大大的範例中，Service 和 Repository 的 DTO 部份就是使用 Dto 和 Entity 來命名。&lt;/p&gt;
&lt;p&gt;故這部份還請各位根據專案慣例作調整，我個人還是習慣用這套命名方式來處理。本範例的架構大致上會長這樣：&lt;/p&gt;
&lt;img src=&#34;https://i.imgur.com/MYmaXpt.jpg&#34; alt=&#34;分層架構03&#34; style=&#34;zoom:50%;&#34; /&gt;
&lt;p&gt;此外，實作部份主要的參考來源為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/search/label/%E5%88%86%E5%B1%A4%E6%9E%B6%E6%A7%8B&#34;&gt;mrkt 的程式學習筆記: ASP.NET MVC 專案分層架構系列&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://raychiutw.github.io/2019/%E9%9A%A8%E6%89%8B-Design-Pattern-2-%E8%BB%9F%E9%AB%94%E5%88%86%E5%B1%A4%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-Software-Layered-Architecture-Pattern/&#34;&gt;軟體分層設計模式 (Software Layered Architecture Pattern) | Ray&amp;rsquo;s Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;公司的新訓文件以及同事 &lt;a href=&#34;https://sunnyday0932.github.io/posts/&#34;&gt;Sian&lt;/a&gt; 的整理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;對想了解分層架構的朋友們，可以也看過 mrkt 大大的操作流程，整體的敘述比較深入。&lt;/p&gt;
&lt;p&gt;但要特別注意，由於我個人平時工作上比較常收到動輒好幾項查詢條件要包裝成單一支 API 的 Parameter DTO 這類需求，故已較為習慣建出一卡車的 DTO 這種方式。&lt;/p&gt;
&lt;p&gt;而像 mrkt 大大的&lt;a href=&#34;https://kevintsengtw.blogspot.com/search/label/%E5%88%86%E5%B1%A4%E6%9E%B6%E6%A7%8B&#34;&gt;系列文&lt;/a&gt;是示範將已有的程式拆分為分層架構的模式、Ray 大大的&lt;a href=&#34;https://github.com/raychiutw/software-layered-architecture-pattern-smaple&#34;&gt;範例程式&lt;/a&gt;的 DTO 命名和這篇的範例不同等等，因為環境、條件和習慣的差別，各家的分層方式和命名都會不太一樣，主要還是看團隊的慣例啦。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;而且畢竟都要寫了，當然要用我習慣的流程來記錄嘛。作者特權！&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;因此本篇的範例，或是說每一篇說明分層的範例在操作的流程、DTO 的建立和使用上都會有所不同。還請有交叉閱讀的朋友們稍加留意。&lt;/p&gt;
&lt;p&gt;不過就像上面引用的 mrkt 大大說的：「『專案分層架構』這個題目相當難以說明，因為要分幾層、怎麼分層、各層有什麼職責、要做什麼事？這些都可以再細分成很多個主題來說明」&lt;/p&gt;
&lt;p&gt;所以希望各位不要拘泥於流程上的順序或是命名之類的，而是理解到這些&lt;strong&gt;都會依照實務上的狀況去做決策和調整&lt;/strong&gt;。最後再重申一次：分層架構是一種分工的概念，所謂「兵無常勢，水無常形」請各位施主見機行事。善哉善哉。&lt;/p&gt;
&lt;p&gt;那麼我們就準備開始囉！&lt;/p&gt;
&lt;h3 id=&#34;大致上的步驟&#34;&gt;大致上的步驟&lt;/h3&gt;
&lt;p&gt;感謝和同事 &lt;a href=&#34;https://sunnyday0932.github.io/posts/&#34;&gt;Sian&lt;/a&gt; 的談話才整理這套步驟，基本上我習慣的流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立 Service 層&lt;/li&gt;
&lt;li&gt;建立 Repository 層&lt;/li&gt;
&lt;li&gt;建立 Controller 及 DTO (Parameter、ViewModel)&lt;/li&gt;
&lt;li&gt;建立 Service 的介面及 DTO (Info、ResultModel)&lt;/li&gt;
&lt;li&gt;建立 Repository 的介面及 DTO (Condition、DataModel)&lt;/li&gt;
&lt;li&gt;將 Service 注入到 Controller, 將 Repository 注入到 Service&lt;/li&gt;
&lt;li&gt;安裝 AutoMapper，後續使用 AutoMapper 處理來處理 DTO 的轉換&lt;/li&gt;
&lt;li&gt;Controller 實作，並接上 Service 的介面&lt;/li&gt;
&lt;li&gt;Service 實作，並接上 Repository 的介面&lt;/li&gt;
&lt;li&gt;Repository 實作&lt;/li&gt;
&lt;li&gt;進行整合測試，呼叫 Controller 試試看是否成功取得資料&lt;/li&gt;
&lt;li&gt;自由發揮&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以注意到其實就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;切出分層&lt;/li&gt;
&lt;li&gt;開介面和用到的 DTO&lt;/li&gt;
&lt;li&gt;用實作銜接各層&lt;/li&gt;
&lt;li&gt;測試&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這樣子的流程。&lt;/p&gt;
&lt;p&gt;但這邊要說明幾點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 3 項的 Controller 我們會沿用本系列的專案，等等會稍作說明&lt;/li&gt;
&lt;li&gt;第 6 項的注入我們會在下一個章節再進行介紹。這邊就先直接使用 new 的方式處理&lt;/li&gt;
&lt;li&gt;同上，由於尚未使用注入，故 8 ~ 10 的實作部分會改從 Repository 開始實作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上部分就…等等實作就會瞭了。那讓我們先介紹一下目前的專案狀況吧！&lt;/p&gt;
&lt;h3 id=&#34;範例專案背景&#34;&gt;範例專案背景&lt;/h3&gt;
&lt;p&gt;接著回到專案的介紹，如果並不是很在乎專案狀況，而是想看實作過程的朋友，請跳到 &lt;a href=&#34;#%E5%BB%BA%E7%AB%8B%E5%88%86%E5%B1%A4&#34;&gt;建立分層&lt;/a&gt; 繼續閱讀。&lt;/p&gt;
&lt;p&gt;我們會使用這個系列的 ProjectN 專案繼續操作。該專案在先前的 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi/&#34;&gt;Api&lt;/a&gt; 和 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper/&#34;&gt;Dapper&lt;/a&gt; 章節中，已經建立了一個對卡牌資料表進行 CRUD 的 &lt;code&gt;CardController&lt;/code&gt;。詳細部份就不附了（畢竟這篇都會打掉嘛）大致上長這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Produces(&amp;#34;application/json&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 查詢卡片的一些操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;     &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Produces(&amp;#34;application/json&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [ProducesResponseType(typeof(CardViewModel), 200)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardViewModel Get(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 查詢指定 ID 的卡片的一些操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 新增卡片的一些操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPut]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Update(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 更新卡片的一些操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpDelete]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Delete(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 刪除卡片的一些操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中用來回傳顯示卡片內容的 Card 類別為了和分層的 DTO 命名一致，已經改為 &lt;code&gt;CardViewModel&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardViewModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片編號&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片描述&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 攻擊力&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 血量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 花費&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;以及當新增及修改卡片時使用的 &lt;code&gt;CardParameter&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片參數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片描述&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 攻擊力&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 血量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 花費&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果像本節開頭提到的，並沒有切分查詢和傳入的 DTO 時，這兩個就有可能使用同一個 Model，遇到這種狀況還請不要太驚慌了。&lt;/p&gt;
&lt;p&gt;不過既然已經提到說不採用共用 Model 而是全部獨立的原因是「常會有多條件的查詢參數」之類的，這邊就補一下查詢列表用的參數吧。&lt;/p&gt;
&lt;p&gt;如此如此，在 &lt;code&gt;Parameter&lt;/code&gt; 資料夾下新增了一個 &lt;code&gt;CardSearchParameter&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片搜尋參數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardSearchParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 攻擊力下限&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinAttack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 攻擊力上限&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxAttack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 血量下限&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinHealth { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 血量上限&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxHealth { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 花費值下限&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinCost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 花費值上限&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxCost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;請注意因為參數是選填的，所以部分欄位使用的是 Nullable。畢竟在卡牌遊戲中，想查詢「攻擊力３以下的卡片」是很平常的需求嘛。&lt;/p&gt;
&lt;p&gt;接著這個參數就放回到 &lt;code&gt;CardController&lt;/code&gt; 的 &lt;code&gt;GetList()&lt;/code&gt; 裡面作為參數吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Produces(&amp;#34;application/json&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetList(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromQuery]&lt;/span&gt; CardSearchParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 查詢卡片的一些操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;畢竟是 GET 方法，標示一下 FromQuery 才是好習慣呦。&lt;/p&gt;
&lt;p&gt;到這邊就說明完目前的專案狀況了，讓我們捲起袖子，開始切分層吧！&lt;/p&gt;
&lt;h3 id=&#34;建立分層&#34;&gt;建立分層&lt;/h3&gt;
&lt;p&gt;接著讓我們先來建立分層吧，這邊採用建立「類別庫」的方式來分層，對於一些比較小的專案，使用資料夾來分層也是沒有問題的。&lt;/p&gt;
&lt;p&gt;首先，讓我們在方案總管對著方案按下右鍵，選擇 加入 → 新增專案&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5kRtiNF.webp&#34;
  alt=&#34;image-20210921153907902&#34;width=&#34;905&#34; height=&#34;582&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著找到類別庫，由於範例專案是使用 .net Core 3.1 的版本，故選擇 .net Core 為目標的類別庫：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rh83s9z.webp&#34;
  alt=&#34;image-20210921154045369&#34;width=&#34;588&#34; height=&#34;619&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們用專案名稱 + Service 來命名這個專案：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2TVpn3L.webp&#34;
  alt=&#34;image-20210921154155709&#34;width=&#34;629&#34; height=&#34;287&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;下個畫面選擇完版本之後，就可以在解決方案看到多了一個 Service 的專案囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3RBmvbp.webp&#34;
  alt=&#34;image-20210921154319429&#34;width=&#34;265&#34; height=&#34;98&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後那個 Class1.cs 可以砍掉，我們不會用到。&lt;/p&gt;
&lt;p&gt;接著請重複以上的步驟，建立 Repository 和 Common 的類別庫：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OwXJY9F.webp&#34;
  alt=&#34;image-20210921154728674&#34;width=&#34;300&#34; height=&#34;169&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果想要把專案做排序或整理的朋友，可以對解決方案右鍵，選擇 加入 → 新增方案資料夾，用資料夾去做排序和整理&lt;/p&gt;
&lt;p&gt;&lt;del&gt;有的時候我都懷疑其他人的分層用 Business 和 Data 命名，該不會是為了排序起來比較好看……？！&lt;/del&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;加入參考&#34;&gt;加入參考&lt;/h3&gt;
&lt;p&gt;建立完類別庫之後，接著我們就要來&lt;strong&gt;加入參考&lt;/strong&gt;，也就是設定這些庫之間的依賴方向。&lt;/p&gt;
&lt;p&gt;一般的分層架構的依賴關係會是從上到下，也就是 展示層 → 商業邏輯層 → 資料存取層，然後他們三個都參考共用層。&lt;/p&gt;
&lt;img src=&#34;https://i.imgur.com/uYlhVjn.jpg&#34; alt=&#34;分層架構05&#34; style=&#34;zoom:33%;&#34; /&gt;
&lt;p&gt;現在讓我們從最外圍開始：對 &lt;code&gt;ProjectN&lt;/code&gt; （或是你 WEB／API 等等所在的專案）右鍵 → 加入 → 專案參考&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SB841e8.webp&#34;
  alt=&#34;image-20210921162356712&#34;width=&#34;941&#34; height=&#34;762&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後讓它參考我們的 Service 層以及共用的 Common：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fawAtxz.webp&#34;
  alt=&#34;image-20210921163054655&#34;width=&#34;420&#34; height=&#34;85&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;同樣地，也請各位對 Service 和 Repository 進行同樣的操作加入參考。各層的參考關係現在應該要是這樣的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API or APP: Service + Common&lt;/li&gt;
&lt;li&gt;Service: Repository + Common&lt;/li&gt;
&lt;li&gt;Repository: Common&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：如果想確認目前專案的相依性，可以在方案總管的解決方案上按下右鍵 → 專案相依性&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;建立介面與-dto&#34;&gt;建立介面與 DTO&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在這個小節，你可能會需要對介面有些了解。還不太了解的朋友，可以參照上個系列的&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface/&#34;&gt;介面篇&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;對於分層是否要使用介面有疑惑的朋友，可以參考 mrkt 大大的這篇 &lt;a href=&#34;https://kevintsengtw.blogspot.com/2013/07/aspnet-mvc.html&#34;&gt;專案分層架構建議&lt;/a&gt; 中的「問題三、一定要用介面嗎？」的段落。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著，讓我們開始著手處理介面的部分吧，先讓我們在 &lt;code&gt;Service&lt;/code&gt; 中新增一個叫做 &lt;code&gt;Interface&lt;/code&gt; 的資料夾來放我們的介面。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/FATU0lt.webp&#34;
  alt=&#34;image-20210921182347662&#34;width=&#34;719&#34; height=&#34;385&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著因為是做卡片管理的 CRUD，當然要用 Card 當前綴。並且由於習慣的關係，只要是 Interface 我會在最前面加上一個 &lt;code&gt;I&lt;/code&gt; 來標示，所以讓我們在該資料夾下建立一個 &lt;code&gt;ICardService&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vr5RTrG.webp&#34;
  alt=&#34;image-20210921194022072&#34;width=&#34;771&#34; height=&#34;417&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CAMytoF.webp&#34;
  alt=&#34;image-20210921194110374&#34;width=&#34;930&#34; height=&#34;650&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如此一來就會建立一個空的介面。啊，記得加上 &lt;code&gt;Public&lt;/code&gt; 呦。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理服務&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ICardService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們補上 CRUD 的五個方法，如果是去 Controller 複製的朋友要注意這邊的 DTO 已經不同囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理服務&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ICardService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IEnumerable&amp;lt;CardResultModel&amp;gt; GetList(CardSearchInfo info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;   &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CardResultModel Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Insert(CardInfo info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardInfo info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因為我們還沒建立對應的 DTO，所以會有紅字也是正常的。我個人平常都是邊捏介面邊開 DTO 就是了。&lt;/p&gt;
&lt;p&gt;畢竟&lt;strong&gt;介面就是各層之間溝通的契約，傳遞的 DTO 當然也就是契約內容了&lt;/strong&gt;。所以在決定這功能要幹嘛的時候就會順便捏起來。&lt;/p&gt;
&lt;p&gt;綜上所述，接著就讓我們來把 DTO 補上吧，因為我們只有 Card 一條線，所以這裡的 DTO 就會是對應展示層的那三個 DTO，反過來說也就是說，如果你的架構裡會有多個 Service 互相協作，或是 Service 需要從多個 Repository 取得資料的話，&lt;strong&gt;諸如此類需要多個部份互相配合的時侯，DTO 就不一定會是和上一層對照起來的了&lt;/strong&gt;。這一點還請注意。&lt;/p&gt;
&lt;p&gt;畢竟這個範例只有單線，已經是最最最簡單的狀況了，還是有蠻多亂七八糟的地方是難以表達的…屆時再請各位切身體會了。&lt;/p&gt;
&lt;p&gt;所以請容我再貼一次九成像的 DTO 吧 XD。此外，因為都九成像了，呈現的時候就先不加上欄位註解囉。&lt;/p&gt;
&lt;p&gt;關於存放的位置，我個人習慣會再開一個 &lt;code&gt;Models&lt;/code&gt; 或是 &lt;code&gt;Dtos&lt;/code&gt; 的資料夾來放這些 DTO，如果功能比較多的就再多一層做個分類。本範例的 DTO 路徑如註解。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ProjectN.Service.Dtos.ResultModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardResultModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ProjectN.Service.Dtos.Info&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardInfo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ProjectN.Service.Dtos.Info&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardSearchInfo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinAttack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxAttack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinHealth { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxHealth { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinCost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxCost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不要忘了要回到 Interface 的地方好好 using 進來呦：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vJSR9Qo.webp&#34;
  alt=&#34;image-20210921200741002&#34;width=&#34;630&#34; height=&#34;266&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我們的 Service 層應該會是這個樣子的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ScSGHFK.webp&#34;
  alt=&#34;image-20210921200831418&#34;width=&#34;273&#34; height=&#34;188&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就讓我們用同樣的節奏來處理 Repository 吧。&lt;/p&gt;
&lt;p&gt;首先仍然是先建立一個 &lt;code&gt;Interface&lt;/code&gt; 資料夾、建立一個 &lt;code&gt;ICardRepository&lt;/code&gt;，並加上我們的 CRUD 五天王：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理服務&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ICardRepository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IEnumerable&amp;lt;CardDataModel&amp;gt; GetList(CardSearchCondition info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;   &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CardDataModel Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Insert(CardCondition info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardCondition info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著是我們的 DTO 們：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ProjectN.Repository.Dtos.DataModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardDataModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ProjectN.Repository.Dtos.Condition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardCondition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ProjectN.Repository.Dtos.Condition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardSearchCondition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinAttack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxAttack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinHealth { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxHealth { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MinCost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int?&lt;/span&gt; MaxCost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊特別提一下 &lt;code&gt;CardSearchCondition&lt;/code&gt; 好了。&lt;/p&gt;
&lt;p&gt;我個人是喜歡先在 Repository 中把一些簡單、跟資料有關的欄位先用 Condition 允許第一次篩選，如果有複雜的、需要別的條件的就再 Service 進行篩選。如此一來如果有別的 Service 需要用差不多的條件查詢該資料的時候，就可以用組合這些參數的方式來重複使用這個 Repository。&lt;/p&gt;
&lt;p&gt;也就是說和 Service 時提到的時候一樣：如果有需要多個資料來源合併處理時，每層的 DTO 就會有所不同。&lt;/p&gt;
&lt;p&gt;這個時候的 Repository 參數應該注重在「&lt;strong&gt;針對這個資料，有哪些基本的過濾條件？&lt;/strong&gt;」下去設計，維持這個篩選的條件是和這個資料來源有關的，這樣才真的能夠&lt;strong&gt;重複利用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;千萬不要為了需求需要多個資料來源，就打破單一職責原則，把所有參數都丟到 Repository 然後才想辦法把資料表之類的兜在一起去滿足需求，這樣就難以複用、本末倒置了。&lt;/p&gt;
&lt;p&gt;如果這樣說可能有點難以理解的話，想像一個「查詢重要訂單」功能的參數同時有「該訂單的交易金額大於五十萬」和「該訂單客戶年收入大於五十萬」這兩個查詢條件，很明顯一個是從訂單本身的資料內容下去篩選、而另一個是從客戶的資料內容下去篩選。&lt;/p&gt;
&lt;p&gt;這種時候就應該是分別去呼叫 訂單的 Repo 和 客戶的 Repo 來拿到相對應的資料，例如說有客戶年收入的條件時，先取出年收入符合的客戶，再用這些客戶的編號取得對應的訂單等等（具體順序看資料大小），那麼這種場合兩者的參數 DTO 就會自然地和 Service 的 DTO 不一樣了。&lt;/p&gt;
&lt;p&gt;請盡量不要為了一路打到底就硬把這兩個條件綑綁在一起，弄成什麼「以交易金額與客戶年收入查詢訂單」的「專用」方法，這樣將來就很難&lt;strong&gt;重複使用&lt;/strong&gt;了。&lt;/p&gt;
&lt;p&gt;（不過凡事都有例外嘛，當你的效能爆炸到被要求只能在 SQL 就先做完篩選的時候，或是只能從舊有功能移植過來的時候，還是要乖乖想辦法就是了囧…）&lt;/p&gt;
&lt;p&gt;聊得遠了，讓我們回到 ProjectN 的 Repository，目前應該會是長這個樣子：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/iNQdSnm.webp&#34;
  alt=&#34;image-20210921204201764&#34;width=&#34;299&#34; height=&#34;184&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;到這邊我們的介面和功能都訂好啦，接著就讓我們開始實作這些介面吧！&lt;/p&gt;
&lt;h3 id=&#34;實作-repository&#34;&gt;實作 Repository&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;註：本篇範例還不會用到依賴注入，因此會將需要用到的依賴對象，例如 Controller 中的 IService 實體直接在建構式裡面建立出來。也因為必須建立實體的關係，故必須從最底的 Repository 開始實作。&lt;/p&gt;
&lt;p&gt;如果是已經有在使用依賴注入的朋友，還請自行在腦內調整一下。不過我個人是覺得實作的順序沒有什麼關係啦，各位朋友有習慣的就按自己習慣的就好溜。&lt;/p&gt;
&lt;p&gt;而還不太知道依賴注入的朋友們，我們&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection/&#34;&gt;下一篇&lt;/a&gt;再來說明。或是也可以先參照&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle/&#34;&gt;依賴反轉原則&lt;/a&gt;的範例自己改看看，此處就先按下不表。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先讓我們從 Repository 開始實作吧！&lt;/p&gt;
&lt;p&gt;第一步就是建立實作的資料夾，方便和 &lt;code&gt;Interface&lt;/code&gt; 做個區隔，所以這邊在 Repository 建立一個 &lt;code&gt;Implement&lt;/code&gt; 資料夾，並在裡面建立 &lt;code&gt;CardRepository&lt;/code&gt; ：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mxPAvRb.webp&#34;
  alt=&#34;image-20210926181827673&#34;width=&#34;226&#34; height=&#34;130&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;建立之後，別忘記把類別改為 &lt;code&gt;Public&lt;/code&gt; 並告訴他我們要實作介面 &lt;code&gt;ICardRepository&lt;/code&gt;（記得 using）：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardRepository&lt;/span&gt; : ICardRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著 IDE 通常都會提醒一下說你有哪些要求沒有做到，這邊就讓我偷懶一下直接讓 IDE 產生空方法：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nJIaMAR.webp&#34;
  alt=&#34;image-20210926182828485&#34;width=&#34;761&#34; height=&#34;267&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardRepository&lt;/span&gt; : ICardRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardDataModel&amp;gt; GetList(CardSearchCondition info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardDataModel Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Insert(CardCondition info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardCondition info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;各位朋友在切出資料存取層的實作時，如果是從舊專案移植過來的，也可以從「把操作資料表的部份都先剪過來」來起手。&lt;/p&gt;
&lt;p&gt;從資料庫裏面操作資料的 CRUD 相關部份我們在上一篇的 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper/&#34;&gt;Dapper&lt;/a&gt; 章節已經做得差不多了，這邊就不再贅述，稍微補上這次新增的查詢列表就行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：為了避免我之後回來抄的時候想不起來，這邊也放一下資料表目前狀況好了：



&lt;img
  src=&#34;https://image.igouist.net/8qhFQBb.webp&#34;
  alt=&#34;image-20211002135411755&#34;width=&#34;304&#34; height=&#34;155&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;seealso cref=&amp;#34;ProjectN.Repository.Interface.ICardRepository&amp;#34; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardRepository&lt;/span&gt; : ICardRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 連線字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _connectString = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;Server=(LocalDB)\MSSQLLocalDB;Database=Newbie;Trusted_Connection=True;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;info&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardDataModel&amp;gt; GetList(CardSearchCondition condition)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM Card&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sqlQuery = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameter = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (condition.MinCost.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sqlQuery.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; Cost &amp;gt;= @MinCost &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parameter.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MinCost&amp;#34;&lt;/span&gt;, condition.MinCost);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (condition.MaxCost.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sqlQuery.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; Cost &amp;lt;= @MaxCost &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parameter.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MaxCost&amp;#34;&lt;/span&gt;, condition.MaxCost);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (condition.MinAttack.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sqlQuery.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; Attack &amp;gt;= @MinAttack &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parameter.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MinAttack&amp;#34;&lt;/span&gt;, condition.MinAttack);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (condition.MaxAttack.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sqlQuery.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; Attack &amp;lt;= @MaxAttack &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parameter.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MaxAttack&amp;#34;&lt;/span&gt;, condition.MaxAttack);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (condition.MinHealth.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sqlQuery.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; Health &amp;gt;= @MinHealth &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parameter.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MinHealth&amp;#34;&lt;/span&gt;, condition.MinHealth);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (condition.MaxHealth.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sqlQuery.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; Health &amp;lt;= @MaxHealth &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parameter.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MaxHealth&amp;#34;&lt;/span&gt;, condition.MaxHealth);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(condition.Name) &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sqlQuery.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; Name LIKE @Name &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            parameter.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;%{condition.Name}%&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (sqlQuery.Any())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sql += &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34; WHERE {string.Join(&amp;#34;&lt;/span&gt; AND &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;, sqlQuery)} &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Query&amp;lt;CardDataModel&amp;gt;(sql, parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardDataModel Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;		
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                SELECT * 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                FROM Card 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                Where Id = @id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.QueryFirstOrDefault&amp;lt;CardDataModel&amp;gt;(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Insert(CardCondition condition)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                INSERT INTO Card 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   [Name]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                  ,[Description]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                  ,[Attack]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                  ,[Health]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                  ,[Cost]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ) 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                VALUES 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                    @Name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,@Description
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,@Attack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,@Health
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,@Cost
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                );
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                SELECT @@IDENTITY;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Execute(sql, condition);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;condition&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardCondition condition)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                UPDATE Card
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                SET 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                    [Name] = @Name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,[Description] = @Description
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,[Attack] = @Attack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,[Health] = @Health
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                   ,[Cost] = @Cost
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                WHERE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                    Id = @id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameters.AddDynamicParams(condition);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Execute(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                DELETE FROM Card
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                WHERE Id = @Id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Execute(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;實作-service&#34;&gt;實作 Service&lt;/h3&gt;
&lt;p&gt;接著是我們 Service 層的實作。要注意的是，&lt;strong&gt;在 DTO 的轉換上，推薦使用 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper/&#34;&gt;AutoMapper&lt;/a&gt; 來處理&lt;/strong&gt;，讓方法關注在商業邏輯本身，而不用被 DTO 的賦值過程洗版。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：對 AutoMapper 不太熟悉的朋友，可以先閱讀 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper/&#34;&gt;AutoMapper —— 類別轉換超省力&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果是在本篇流程不打算使用 AutoMapper 的朋友，請在使用到 AutoMapper 的場合自行 new 出目標物件並賦值即可。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先一樣先建立實作用的資料夾，並建立 &lt;code&gt;CardService&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rJ4rMgs.webp&#34;
  alt=&#34;image-20211003091808904&#34;width=&#34;191&#34; height=&#34;136&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且讓 IDE 幫忙把要實作的介面都先列出來：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardService&lt;/span&gt; : ICardService
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardResultModel&amp;gt; GetList(CardSearchInfo info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardResultModel Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Insert(CardInfo info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardInfo info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; NotImplementedException();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那因為這個範例還沒有什麼需要注意的商業邏輯，因此我們就先在 Service 做一個承上（Controller）啟下（Repository）的動作。&lt;/p&gt;
&lt;p&gt;也就是說每個方法負責去接收 Controller 的請求，並呼叫 Repository 來完成工作，並用 AutoMapper 來進行過程的轉換。&lt;/p&gt;
&lt;p&gt;因此我們的 Service 要能夠呼叫到 Repository 和 Mapper，現在先讓我們把 Repository 當作私有成員建立進來：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardService&lt;/span&gt; : ICardService
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ICardRepository _cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardService()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 其他實作部分&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著是 DTO 的對映部分，先建立 Service 的對映表。我個人習慣跟隨公司慣例開一個 &lt;code&gt;Mappings&lt;/code&gt; 資料夾來放：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/wwEi2pW.webp&#34;
  alt=&#34;image-20211003094809104&#34;width=&#34;248&#34; height=&#34;129&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：當專案還有 Mappings, Enum（列舉）和其他設定檔等等，資料夾就會變得挺多的。&lt;/p&gt;
&lt;p&gt;這種時候，也可以把這類基礎建設相關的都放到 &lt;code&gt;Infrastructure&lt;/code&gt; 資料夾做個整理，閱讀和操作上會比較舒服。&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ServiceMappings&lt;/span&gt; : Profile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; ServiceMappings()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Info -&amp;gt; Condition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.CreateMap&amp;lt;CardInfo, CardCondition&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.CreateMap&amp;lt;CardSearchInfo, CardSearchCondition&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// DataModel -&amp;gt; ResultModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.CreateMap&amp;lt;CardDataModel, CardResultModel&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們也把 AutoMapper 的部份放到建構式裡面吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardService&lt;/span&gt; : ICardService
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IMapper _mapper;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ICardRepository _cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardService()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			cfg.AddProfile&amp;lt;ServiceMappings&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 其他實作部分&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就化身為無情的串接機器，把各個實作接起來。&lt;/p&gt;
&lt;p&gt;例如說 查詢列表 &lt;code&gt;GetList&lt;/code&gt;，標準流程就是 轉換參數 DTO、呼叫目標方法、轉換回傳 DTO：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;info&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardResultModel&amp;gt; GetList(CardSearchInfo info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; condition = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardSearchInfo, CardSearchCondition&amp;gt;(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.GetList(condition);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IEnumerable&amp;lt;CardDataModel&amp;gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    	IEnumerable&amp;lt;CardResultModel&amp;gt;&amp;gt;(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當然實際上還會根據需求，在這邊做一些商業邏輯的處理，例如說呼叫多個 Repository 方法、參數內容換成內部商業邏輯定義好的代號等等。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;叮嚀一下：如果感覺公開方法裡面做的事情太多的話，還請考慮是不是職責太複雜、並嘗試適當地拆出私有方法或其他類別呦。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;那麼這邊就直接補上剩下的方法，貼上整個 &lt;code&gt;CardService&lt;/code&gt; 吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;seealso cref=&amp;#34;ProjectN.Service.Interface.ICardService&amp;#34; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardService&lt;/span&gt; : ICardService
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IMapper _mapper;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ICardRepository _cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardService()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			cfg.AddProfile&amp;lt;ServiceMappings&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;info&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardResultModel&amp;gt; GetList(CardSearchInfo info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; condition = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardSearchInfo, CardSearchCondition&amp;gt;(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; cards = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.GetList(condition);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IEnumerable&amp;lt;CardDataModel&amp;gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	IEnumerable&amp;lt;CardResultModel&amp;gt;&amp;gt;(cards);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardResultModel Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; card = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Get(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardDataModel, CardResultModel&amp;gt;(card);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;info&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Insert(CardInfo info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; condition = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardInfo, CardCondition&amp;gt;(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Insert(condition);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;info&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardInfo info)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; condition = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;CardInfo, CardCondition&amp;gt;(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Update(id, condition);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Delete(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;實作-controller&#34;&gt;實作 Controller&lt;/h3&gt;
&lt;p&gt;最後就是讓我們的 Controller 來接上 Service 的介面，把方法公開出去啦！&lt;/p&gt;
&lt;p&gt;同樣的也先放一下 Mappings：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ControllerMappings&lt;/span&gt; : Profile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; ControllerMappings()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Parameter -&amp;gt; Info&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.CreateMap&amp;lt;CardParameter, CardInfo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.CreateMap&amp;lt;CardSearchParameter, CardSearchInfo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ResultModel -&amp;gt; ViewModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.CreateMap&amp;lt;CardResultModel, CardViewModel&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後一樣把 Controller 的各方法補上：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片管理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;seealso cref=&amp;#34;Microsoft.AspNetCore.Mvc.ControllerBase&amp;#34; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; IMapper _mapper;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ICardService _cardService;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardController()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	cfg.AddProfile&amp;lt;ControllerMappings&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardService();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Produces(&amp;#34;application/json&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetList(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromQuery]&lt;/span&gt; CardSearchParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            CardSearchParameter, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	CardSearchInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; cards = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.GetList(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IEnumerable&amp;lt;CardResultModel&amp;gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	IEnumerable&amp;lt;CardViewModel&amp;gt;&amp;gt;(cards);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;remarks&amp;gt;我是附加說明&amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;response code=&amp;#34;200&amp;#34;&amp;gt;回傳對應的卡片&amp;lt;/response&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;response code=&amp;#34;404&amp;#34;&amp;gt;找不到該編號的卡片&amp;lt;/response&amp;gt;          &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Produces(&amp;#34;application/json&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [ProducesResponseType(typeof(CardViewModel), 200)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardViewModel Get(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; card = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Get(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            CardResultModel,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	CardViewModel&amp;gt;(card);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            CardParameter,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isInsertSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Insert(info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isInsertSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPut]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Update(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; targetCard = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Get(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (targetCard &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; NotFound();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            CardParameter,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        	CardInfo&amp;gt;(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isUpdateSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Update(id, info);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isUpdateSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpDelete]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Delete(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardService.Delete(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;測試一下有沒有接上去&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IHsfTjU.webp&#34;
  alt=&#34;image-20211003114955056&#34;width=&#34;1428&#34; height=&#34;908&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;打完收工！&lt;/p&gt;
&lt;h2 id=&#34;自問自答心得篇&#34;&gt;自問自答心得篇&lt;/h2&gt;
&lt;p&gt;實作結束之後，這邊就留個版面放一些上面沒提到／塞不進去／老人碎碎念的部份。&lt;/p&gt;
&lt;p&gt;感謝 &lt;a href=&#34;https://sunnyday0932.github.io/2020/%E4%B8%89%E5%B1%A4%E5%BC%8F%E6%9E%B6%E6%A7%8B/&#34;&gt;Sian&lt;/a&gt; 提供的建議和示範，這邊補上一些關於我個人分層上遇到的一些問題和想法，整理成Ｑ＆Ａ的方式。&lt;/p&gt;
&lt;h3 id=&#34;關於重複建立-dto-的問題&#34;&gt;關於重複建立 DTO 的問題&lt;/h3&gt;
&lt;p&gt;Q: 資料處理給商業邏輯要開一個 DataModel，商業邏輯出去又要開一個 ResultModel……每次都要開一堆重複的 DTO 很麻煩！一定要這樣做嗎？&lt;/p&gt;
&lt;p&gt;A: 不一定。&lt;/p&gt;
&lt;p&gt;我這邊也遇過一些系統，由於場景相對單純，存取上基本只有增修查改，商業邏輯也多是驗證和內容資料的處理，因此就採用一個 Dto 貫穿三層的方式進行。例如說跟訂單有關的就只使用一個 OrderDto、跟產品有關的就只用一個 ProductDto 這樣，整體用起來會比較像是一個實體對應一個 Dto 的感覺。&lt;/p&gt;
&lt;p&gt;我個人認為「要不要把各層之間溝通用的 DTO 都獨立出來」，基本上就是在問這個系統：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;會不會有「你對資料表比較熟悉，負責資料存取層；我對外制定規格，負責展示層」等等，這種&lt;strong&gt;需要針對分層去分工的時候？&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;每一層之間傳遞的 Model 是否會在各層進一步加工，&lt;strong&gt;每一層需要傳遞的資料會不會有所不同？&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;每一層之間呼叫方法的參數是否會不同，&lt;strong&gt;有沒有需要把參數封裝成一個物件？&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;資料存取層將資料表的資料查詢出來，但該功能展示給使用者的時候並不需要這麼多欄位&lt;/li&gt;
&lt;li&gt;商業邏輯層需要針對這個內容去和別的資料進行組裝、運算
&lt;ul&gt;
&lt;li&gt;例如說需求不只是單純的客戶查詢，而是客戶平均消費排行榜之類的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;展示層需要進行一些給使用者介面顯示時的調整
&lt;ul&gt;
&lt;li&gt;浮點數顯示的時候只到小數後兩位之類的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;像這種分工明確的情況下，就很容易會遇到每一層之間傳遞的 Dto 內容必須不同的情況。&lt;/p&gt;
&lt;p&gt;以上的狀況如果都共用同一個 DTO 反而綁手綁腳的，把各層之間盡責地拆分開來，更可以降低彼此間的耦合，讓修改的範圍變小、並盡量只在符合該職責的地方修改，整體會比較靈活。&lt;/p&gt;
&lt;p&gt;回到問題上來說，要不要確實地把每一層的 DTO 做拆分？或是想採用一個 DTO 代表該資料來貫穿整個系統？我個人覺得都還是蠻彈性的，可以根據系統的複雜度去嘗試。&lt;/p&gt;
&lt;p&gt;如果你可能會根據分層去指派分工，又或者是有些地方會頻繁地修改、每一層之間傳遞的資訊常常會有所差異，甚至是收到的需求常常挺客製化的時候，拆分開來在往後的修改就可能可以迴避一些耦合上的問題。但相對的，因為 Model 終究還是變多了，有時候也會遇到重複且多餘的修改。&lt;/p&gt;
&lt;p&gt;反過來說如果系統的工作單一，場景相對單純，例如說是針對某個職責去架設的、提供給其他系統使用的小型服務。那麼先採用 DTO 對映資料內容並共用的做法，在開發上反而比較迅速。&lt;/p&gt;
&lt;p&gt;就再請各位使用時細細品味吧。&lt;del&gt;我都是前輩怎麼開我就怎麼寫啦&lt;/del&gt;&lt;/p&gt;
&lt;h3 id=&#34;三層式架構跟-mvc-一樣嗎&#34;&gt;三層式架構跟 MVC 一樣嗎？&lt;/h3&gt;
&lt;p&gt;這個吼，不太一樣啦（抓頭）&lt;/p&gt;
&lt;p&gt;雖然兩者的目標都算是「區分職責＋解除耦合」的感覺，但並不是一樣的東西。&lt;/p&gt;
&lt;p&gt;MVC 是一種框架，主要分為 View, Controller, Model，特別強調此三者並不能直接類比到三層式架構的三層。我們從兩邊的角度來比對一下：&lt;/p&gt;
&lt;p&gt;以 MVC 的角度出發，對應三層式中的商業邏輯和資料存取都是塞到 Model 中處理的（當然也會遇到全部塞在 Controller 的朋友囧），這時候最大的差別就是有沒有區分出商業邏輯。畢竟三層式就是為了要能重複使用而分層的嘛。&lt;/p&gt;
&lt;p&gt;反過來從三層式架構的角度出發的話，MVC 也只是用在展示層的一種模式而已。例如說展示層使用 MVC 的架構，往下接到商業邏輯等等。實際上使用分層的話，最終不管展示層是 MVC 框架、WebForm、Web Api 等等，對底下的商業邏輯層等等都不會有任何影響。&lt;/p&gt;
&lt;p&gt;因此兩者是可以並存的不同層級下的&lt;strong&gt;不同拆分方式&lt;/strong&gt;，並不能簡單地一概而論。&lt;/p&gt;
&lt;p&gt;這個問題也可以參考以下文章，分享給大家：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://shunnien.github.io/2017/07/29/3-tier-and-mvc-introduction/&#34;&gt;三層結構與 Asp.Net MVC 的簡介 | ShunNien&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://social.msdn.microsoft.com/Forums/expression/zh-TW/9b08c038-e365-449a-bd24-1a0771a525c2/3553121839webform33287mvc1997723652243352655027083303403526424565?forum=236&#34;&gt;請問 WebForm 與 MVC 三層式架構的觀念問題 (microsoft.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;service-可以依賴-service-嗎&#34;&gt;Service 可以依賴 Service 嗎？&lt;/h3&gt;
&lt;p&gt;可以。&lt;del&gt;既然人家都寫好了不用白不用&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;基本上來說在兩個 Service 之間就是一般物件和物件的關係，如果需要對方的公開方法，當然可以依賴對方。&lt;/p&gt;
&lt;p&gt;不過為了避免循環參考，和同事經過了一些討論，還是有一些地方可以注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果只是純粹的流程控制，可以把工作還給流程控制的 Controller，讓該功能的 Controller 依序呼叫 Service 處理&lt;/li&gt;
&lt;li&gt;如果是多個商業邏輯的整合，可以用一個高階的 Service 去整合負責較小職責的 Service，避免循環參考或則是依賴關係混亂&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外在使用上也要注意呼叫對象是否有經過一些複雜的商業處理，如果那正是你要的，例如說你就是要訂單計算之後的結果，那當然沒有問題；倘若只是需要乾淨的資料，也可以乾脆往下去依賴 Repository 就可以，避免商業邏輯間意料之外的耦合。&lt;/p&gt;
&lt;p&gt;所以請不要被從上到下這個方向束縛了，Service 也可以是厚厚的一層。&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;本篇記錄了為什麼要分層，以及我個人平常開新專案的分層步驟。&lt;/p&gt;
&lt;p&gt;最後針對分層架構，總結一下幾個筆記重點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分層是為了&lt;strong&gt;分離關注點&lt;/strong&gt;。讓每一層的職責明確、專注在各自的工作&lt;/li&gt;
&lt;li&gt;分層帶來的好處：
&lt;ul&gt;
&lt;li&gt;修改時能更快鎖定目標、縮小範圍&lt;/li&gt;
&lt;li&gt;適合多人合作，提升開發效率&lt;/li&gt;
&lt;li&gt;增加程式碼的複用性&lt;/li&gt;
&lt;li&gt;必要的時候可以抽換掉某一層&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;分層的前提：
&lt;ul&gt;
&lt;li&gt;物件的設計要遵守 SOLID 原則&lt;/li&gt;
&lt;li&gt;使用依賴注入來解除層與層之間的依賴&lt;/li&gt;
&lt;li&gt;額外的開發成本&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;常見的三層式架構：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;展示層&lt;/strong&gt;：負責和外部使用者互動&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;商業邏輯層&lt;/strong&gt;：負責處理商業規則和相關的邏輯處理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;資料存取層&lt;/strong&gt;：負責存取資料的相關操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共用層&lt;/strong&gt;：負責擴充方法等不屬於任何一層的共用模組&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;資料傳輸物件（DTO）
&lt;ul&gt;
&lt;li&gt;層與層之間需要資料的傳遞，因此我們會建立只有欄位沒有方法的 DTO 來傳輸資料&lt;/li&gt;
&lt;li&gt;DTO 的轉換挺麻煩的，這時候就可以考慮使用 AutoMapper 這類工具&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;分層本身是分工的概念
&lt;ul&gt;
&lt;li&gt;要分成幾層、每一層負責什麼工作，這些都是需要決策的&lt;/li&gt;
&lt;li&gt;綜上所述，請根據專案需求和團隊慣例作調整&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大概這樣。&lt;/p&gt;
&lt;p&gt;實作的部份也因為分層這個題目太廣了，其實有點不太知道要怎麼寫比較好，放置了一段時間。&lt;/p&gt;
&lt;p&gt;最後決定就接續先前文章的進度，用平常習慣的方式調整一下來跑完一輪。當然內容還有許多地方是需要調整的：例如我們下一篇要加入的依賴注入，或是將方法改寫為非同步等等。&lt;/p&gt;
&lt;p&gt;內文的範例也延續了單純的 CRUD，並沒有完整展現商業邏輯分工出來的魅力，算是一些小遺憾。最後補充一些本篇的參考資料。如果有想要補充或討論的朋友，也歡迎分享您的看法，感謝感謝。&lt;/p&gt;
&lt;p&gt;那麼，我們下篇再見囉～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2012/10/aspnet-mvc-part1.html&#34;&gt;mrkt 的程式學習筆記: ASP.NET MVC 專案分層架構 Part.1 初學者的起手式 (kevintsengtw.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://raychiutw.github.io/2019/%E9%9A%A8%E6%89%8B-Design-Pattern-2-%E8%BB%9F%E9%AB%94%E5%88%86%E5%B1%A4%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-Software-Layered-Architecture-Pattern/&#34;&gt;隨手 Design Pattern (2) - 軟體分層設計模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.johnwu.cc/article/software-layered-architecture-pattern.html&#34;&gt;軟體分層架構模式 | John Wu&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/%E4%B8%89%E5%B1%A4%E5%BC%8F%E6%9E%B6%E6%A7%8B/&#34;&gt;三層式架構 (sunnyday0932.github.io)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/web-forms/overview/data-access/introduction/creating-a-business-logic-layer-cs&#34;&gt;建立商務邏輯層（C#） | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://shunnien.github.io/2017/07/29/3-tier-and-mvc-introduction/&#34;&gt;三層結構與 Asp.Net MVC 的簡介 | ShunNien&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@steph.c/%E4%B8%89%E5%B1%A4%E6%9E%B6%E6%A7%8B%E6%98%AF%E4%BB%80%E9%BA%BC-%E6%88%91%E5%8F%AA%E7%9F%A5%E9%81%93%E4%B8%89%E5%B1%A4%E8%82%89-efe542c38aaf&#34;&gt;MVC 三層架構 是什麼? 我只知道三層肉. 三層架構 (3-Tier Architecture)是哪三層 ? | by Steph Dev 史帝夫和戴夫 | Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10227123&#34;&gt;DDD 戰術設計：Domain Service - iT 邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2013/07/aspnet-mvc.html&#34;&gt;mrkt 的程式學習筆記: ASP.NET MVC 專案分層架構 - 建議與補充說明 (kevintsengtw.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;本系列文章&#34;&gt;本系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞抓蟲: 使用 FromUri 的複雜型別在有傳遞 QueryString 的情況下會先建立再賦值</title>
      <link>https://igouist.github.io/post/2021/08/set-default-value-with-model-when-fromuri/</link>
      <pubDate>Thu, 19 Aug 2021 22:20:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/08/set-default-value-with-model-when-fromuri/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TuGp6Lk.webp&#34;
  alt=&#34;Image&#34;width=&#34;600&#34; height=&#34;883&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;事發緣由&#34;&gt;事發緣由&lt;/h2&gt;
&lt;p&gt;在 .Net Framework 4.6.2 MVC 的 ApiController 中，某個查詢資料列表的方法除了提供查詢條件的參數以外，還有提供選擇性的分頁參數。也就是像這樣子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Boo&amp;gt; GetBoos(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; SearchBooParameter parameter,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; PagingParameter paging = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫 Service 查資料...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由於需要調整該功能的預設排序，改為由大到小，又不想背負更改大量共用的 &lt;code&gt;PagingParameter&lt;/code&gt; 去影響到其他使用到的地方，決定在 Controller 這裡簡單用預測值加上判斷處理一下就好&lt;/p&gt;
&lt;p&gt;相信著「若使用者沒有傳遞 paging 相關的參數，應該就會是給定的預設值 &lt;code&gt;null&lt;/code&gt; 吧！」的我，用了 &lt;code&gt;if (paging is null)&lt;/code&gt; 進行判斷：若是 &lt;code&gt;null&lt;/code&gt; 的情況就將其中用來標示排序方向的成員 &lt;code&gt;isDesc&lt;/code&gt; 設定為 true，開開心心交差。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Boo&amp;gt; GetBoos(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; SearchBooParameter parameter,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; PagingParameter paging = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (paging &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        paging = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; PagingParameter();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        paging.isDesc = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 預設由大到小&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫 Service 查資料...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但實際使用之後發現：即使只有傳入查詢條件參數、未傳遞 paging 時，資料仍然由小到大顯示，且 &lt;code&gt;paging.isDesc&lt;/code&gt; 竟然是 false，並未被更改到。也就是說，即使未傳遞 paging，它也並不是 null！&lt;/p&gt;
&lt;p&gt;實測之後發現：若在呼叫該 API 的時候，給定一個完全無關的參數，例如 &lt;code&gt;?a=1&lt;/code&gt;，則 paging 還是會被建立一個實體出來，並無視 &lt;code&gt;= null&lt;/code&gt; 這個預設值。因此就導致了非預期（＝跟我想的不一樣啊！）的行為。&lt;/p&gt;
&lt;p&gt;這邊直接先講結論：&lt;strong&gt;如果有傳遞 QueryString 的任何參數時，不管這些參數跟指定的類別有沒有關係，放在 &lt;code&gt;[FromUri]&lt;/code&gt; 的複雜型別都會先建立出實體，再嘗試和 QueryString 的內容進行比對與設值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;設定在 &lt;code&gt;[FromUri]&lt;/code&gt; 的複雜型別身上的預設值，像是 &lt;code&gt;[FromUri] PagingParameter paging = null&lt;/code&gt; 只有完全沒給任何 QueryString 的時候才會吃到。（不過因為預設值只能是常數的關係，基本上就是指 defualt 的 null）&lt;/p&gt;
&lt;p&gt;因此如果遇到要給定預設值的場合，還是得乖乖地針對型別中的成員做設定比較保險，例如 &lt;code&gt;bool isDesc { get; set; } = true&lt;/code&gt;。另外，因為完全沒給 QueryString 的時候還是會是 null，故該有的參數檢查仍然不能漏了。&lt;/p&gt;
&lt;h2 id=&#34;測試案例&#34;&gt;測試案例&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;.Net Framework 4.7.2 Web Api - FromUri&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[RoutePrefix(&amp;#34;api&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValuesController&lt;/span&gt; : ApiController
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet, Route(&amp;#34;Test&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Test([FromUri] SimplyParameter parameter = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; parameter?.IsSuccess.ToString() ?? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;It&amp;#39;s NULL!&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimplyParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; IsSuccess { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Message { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/api/test&lt;/code&gt; =&amp;gt; It&amp;rsquo;s NULL!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/api/test?a=2&lt;/code&gt; =&amp;gt; False&lt;/p&gt;
&lt;p&gt;若改為必須參數，而不是選擇性參數呢：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet, Route(&amp;#34;Test&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Test([FromUri] SimplyParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; parameter?.IsSuccess.ToString() ?? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;It&amp;#39;s NULL!&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/api/test&lt;/code&gt; =&amp;gt; It&amp;rsquo;s NULL!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/api/test?a=2&lt;/code&gt; =&amp;gt; False&lt;/p&gt;
&lt;p&gt;看起來最大的差異點在於&lt;strong&gt;有沒有給 QueryString&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;現在讓我們對 Model 給定預設值，並加入建構式並觀察執行順序：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[RoutePrefix(&amp;#34;api&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValuesController&lt;/span&gt; : ApiController
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet, Route(&amp;#34;Test&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Test([FromUri] SimplyParameter parameter = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; parameter?.IsSuccess.ToString() ?? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;It&amp;#39;s NULL!&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimplyParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; SimplyParameter()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;m Creating!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; IsSuccess { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Message { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;It&amp;#39;s me!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/api/test?a=2&lt;/code&gt; =&amp;gt; True&lt;/p&gt;
&lt;p&gt;並且在 &amp;ldquo;It&amp;rsquo;s me!&amp;rdquo; 下中斷點，可以觀察到在進入 Test 前，的確建立了 SimplyParameter 並給予預設值&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/api/test?IsSuccess=true&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;中斷點的執行順序為：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立 SimplyParameter&lt;/li&gt;
&lt;li&gt;執行 &lt;code&gt;public bool IsSuccess = true;&lt;/code&gt; 給予初始值&lt;/li&gt;
&lt;li&gt;執行 &lt;code&gt;public string Message = &amp;quot;It&#39;s me!&amp;quot;&lt;/code&gt; 給予初始值&lt;/li&gt;
&lt;li&gt;呼叫 &lt;code&gt;SimplyParameter()&lt;/code&gt; 建構式，觸發 &lt;code&gt;&amp;quot;I&#39;m Creating!&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;呼叫 &lt;code&gt;IsSuccess&lt;/code&gt; 的 &lt;code&gt;set&lt;/code&gt;，將 QueryString 傳遞的值塞進去&lt;/li&gt;
&lt;li&gt;對於 QueryString 未提供的 &lt;code&gt;Message&lt;/code&gt;，則未呼叫其 &lt;code&gt;set&lt;/code&gt;，保持預設值&lt;/li&gt;
&lt;li&gt;回到 &lt;code&gt;string Test()&lt;/code&gt; 方法，&lt;code&gt;SimplyParameter&lt;/code&gt; 已建立，無視 &lt;code&gt;parameter = null&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;看起來當有傳遞 QueryString 的時候，就會先建立需要的 Model 再逐一嘗試設值。&lt;/p&gt;
&lt;p&gt;接著讓我們試試看，當 &lt;code&gt;[FromUri]&lt;/code&gt; 的複雜型別不只一個的時候會怎麼運作呢：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[RoutePrefix(&amp;#34;api&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValuesController&lt;/span&gt; : ApiController
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet, Route(&amp;#34;Test&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Test(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromUri]&lt;/span&gt; SimplyParameter parameter = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromUri]&lt;/span&gt; AnotherSimplyParameter parameterAnother = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; parameter?.IsSuccess.ToString() ?? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;It&amp;#39;s NULL!&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimplyParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; SimplyParameter()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;m Creating!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; IsSuccess { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Message { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;It&amp;#39;s me!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AnotherSimplyParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; AnotherSimplyParameter()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I, Another me, are Creating!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Idx { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#ae81ff&#34;&gt;999&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;WOW&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/api/test?a=2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;SimplyParameter 和 AnotherSimplyParameter 會依照 &lt;code&gt;Test()&lt;/code&gt; 中傳入的順序，各自經歷一次上述的流程，故兩者都不會是 Null&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/api/test?IsSuccess=true&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;即使傳入的參數只有其中一個 Model 具有符合的欄位，但由於上述的順序是「先建立，再比對」，故仍然會按上述流程分別建立兩者，仍然不會是 Null&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/api/test?parameter.IsSuccess=true&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;即使已經具名指定了 SimplyParameter，但動作和 &lt;code&gt;IsSuccess=true&lt;/code&gt; 仍然一樣&lt;/p&gt;
&lt;p&gt;最後，我們加入一個簡單型別試試看，也就是改為如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Test(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; SimplyParameter parameter = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; AnotherSimplyParameter parameterAnother = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; hello = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;world!&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/api/test&lt;/code&gt; =&amp;gt; hello = &amp;ldquo;world!&amp;rdquo;;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/api/test?a=2&lt;/code&gt; =&amp;gt; hello = &amp;ldquo;world!&amp;rdquo;;&lt;/p&gt;
&lt;p&gt;簡單型別的預設值在運作上非常直覺，沒有什麼太大的問題。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet, Route(&amp;#34;Test&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Test(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; hello,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; SimplyParameter parameter = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromUri]&lt;/span&gt; AnotherSimplyParameter parameterAnother = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/api/test?hello=A&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;即使我們已經給了作為必要參數的簡單型別值，但由於同樣是從 Uri 來的，並不會在把值丟給簡單型別後就停止，兩個可選的複雜型別仍然會被建立&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在指定複雜型別為 &lt;code&gt;[FromUri]&lt;/code&gt; 的場合，有以下注意事項
&lt;ul&gt;
&lt;li&gt;有傳遞 QueryString 的參數時，&lt;strong&gt;放在 &lt;code&gt;[FromUri]&lt;/code&gt; 的複雜型別會先建立出實體，再嘗試和 QueryString 的內容進行比對與設值&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;也因為會先建立實體，故選擇性參數的預設值，例如 &lt;code&gt;parameter = null&lt;/code&gt; 或是 &lt;code&gt;parameter = default&lt;/code&gt; 並沒有效果，而是以該類型中各成員的預設值為主&lt;/li&gt;
&lt;li&gt;因此如果遇到要針對各參數給定初始值的場合，請不要直接設定在 &lt;code&gt;[FromUri]&lt;/code&gt; 的複雜型別身上再偷雞手動給值，而是乖乖地針對型別中的成員做設定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parameter = default&lt;/code&gt; 當且僅當沒有傳遞任何 QueryString 的時候才有效（不過原本都沒給就會是 Default ，也就是 null，所以有沒有效我肉眼也分不太出來）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;然後關於一開始的問題，最後認命地請使用端傳參數解決了，耶嘿&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同場加映&#34;&gt;同場加映&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;.Net Core 3.1 Web Api - FromQuery&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TestController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Get([FromQuery] SimplyParameter parameter = &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; parameter?.IsSuccess.ToString() ?? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;It&amp;#39;s NULL!&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimplyParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; IsSuccess { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Message { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/test&lt;/code&gt; =&amp;gt; False&lt;/p&gt;
&lt;p&gt;不愧是 Core，直接就建實體了，畢竟都說是 &lt;code&gt;FromQuery&lt;/code&gt; 了嘛。既然都直接說會從 Query 來了，就沒在跟你五四三看有沒有 QueryString 的啦 XD&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.huanlintalk.com/2013/01/aspnet-web-api-parameter-binding.html&#34;&gt;ASP.NET Web API 參數繫結 - Huan-Lin 學習筆記 (huanlintalk.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api&#34;&gt;ASP.NET Web API 中的參數系結-ASP.NET 4.x | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞抓蟲: Url 變得怪怪的？你可能是零寬空格（ZWSP）的受害者！</title>
      <link>https://igouist.github.io/post/2021/06/zero-width-space/</link>
      <pubDate>Sat, 26 Jun 2021 21:53:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/06/zero-width-space/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bS6EGIL.webp&#34;
  alt=&#34;&#34;width=&#34;600&#34; height=&#34;600&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這週遇到個想不到的坑，特別來記錄一下。故事是這樣的－－&lt;/p&gt;
&lt;p&gt;在需要呼叫其他 API 服務時，發生了以下怪事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打某支查詢 API，突然查不到任何東西，或是跳出參數錯誤&lt;/li&gt;
&lt;li&gt;有些&lt;strong&gt;需要用參數組成 URL 的 API 跑出 Not Found&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;第一組資料呼叫成功，第二組突然路徑錯誤&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;寫入的時候，&lt;strong&gt;資料莫名其妙多了個 &lt;code&gt;?&lt;/code&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;例如原先的資料是 &lt;code&gt;ABC&lt;/code&gt;，不知怎地變成了 &lt;code&gt;ABC?&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由於這些操作都涉及到同一個參數，直覺上就是我們這邊給的參數出了點問題，馬上進入找犯人的環節。直接中斷點標記下去，反覆觀察該字串，但它就是一個普通的字串 &lt;code&gt;&amp;quot;ABC&amp;quot;&lt;/code&gt;，完全看不出什麼端倪。&lt;/p&gt;
&lt;p&gt;正要覺得參數沒有問題的時候，赫然發現組出來的 Url 相當不對勁：在該參數的後方，多出了 &lt;strong&gt;&lt;code&gt;%E2%80%8B&lt;/code&gt;&lt;/strong&gt; 這串神秘東西！&lt;/p&gt;
&lt;p&gt;當下我驚呆了，我們傳出去的 Url 裡，並不是預想的 &lt;code&gt;/api/product/ABC&lt;/code&gt;，而是 &lt;code&gt;/api/product/ABC%e2%80%8b&lt;/code&gt;！真是赤裸裸的背叛！這串鬼東西到底是什麼來頭？！&lt;/p&gt;
&lt;p&gt;一查下去，原來這東西叫做 &lt;strong&gt;零寬空格（Zero-width space, ZWSP）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;顧名思義，就是完全沒有寬度的空白字元。這東西在 Unicode 叫做 &lt;strong&gt;&lt;code&gt;U+200B&lt;/code&gt;&lt;/strong&gt;，我們比較常見到的是編碼之後的樣子 &lt;strong&gt;&lt;code&gt;%e2%80%8b&lt;/code&gt;&lt;/strong&gt; 或 &lt;strong&gt;&lt;code&gt;\xe2\x80\x8b&lt;/code&gt;&lt;/strong&gt;。他還有另外兩個兄弟 &lt;code&gt;U+200C&lt;/code&gt;、&lt;code&gt;U+200D&lt;/code&gt;，平常在泰文、高棉文之類的地方工作，這東西的特色就是：&lt;strong&gt;肉眼不可見、殺人於無形&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它有多可怕，讓我們直接用 Linqpad 來試看看吧。&lt;/p&gt;
&lt;p&gt;現在我們有選手 A 和選手 B，其中選手 A 偷偷嗑了禁藥 ZWSP：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; a = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ABC&amp;#34;&lt;/span&gt; + &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;\&lt;/span&gt;u200B&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; b = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ABC&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;讓我們打印出來看看：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;a:{a}&amp;#34;&lt;/span&gt;.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;b:{b}&amp;#34;&lt;/span&gt;.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Z9VihgB.webp&#34;
  alt=&#34;Image&#34;width=&#34;164&#34; height=&#34;75&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看起來完全一模一樣，連反白都分辨不出來！&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.Length.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;b.Length.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看來其中一個傢伙明顯比較長。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(a == b).Dump();      &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(a.Equals(b)).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看來這兩個傢伙完全不一樣！&lt;/p&gt;
&lt;p&gt;目前看來，從長度和比較運算等方面都會發現它們並不一樣，但是肉眼卻分不出來。&lt;/p&gt;
&lt;p&gt;這樣就會產生一些看起來像是 &lt;code&gt;(&amp;quot;ABC&amp;quot; == &amp;quot;ABC&amp;quot;) // False&lt;/code&gt; 的神奇場景，除了揉眼睛然後哭喊「明明就一樣」以外無從下手。更可怕的是當我們拿去組 Url，問題就出來啦：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HttpUtility.UrlEncode(a).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// ABC%e2%80%8b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HttpUtility.UrlEncode(b).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// ABC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;真是完蛋。&lt;/p&gt;
&lt;p&gt;同時在查資料的時候，也發現 NET Framework 3.5 之後的 &lt;code&gt;Trim()&lt;/code&gt; 並不把這個零寬空格當成空白，因此單純用 &lt;code&gt;Trim()&lt;/code&gt; 是不會把這鬼東西砍掉的。&lt;/p&gt;
&lt;p&gt;所以如果你有以下情況，你可能是零寬空格的受害者！&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存到資料庫的資料莫名多一個 &lt;code&gt;?&lt;/code&gt;（有看不見或編碼錯誤的字元）&lt;/li&gt;
&lt;li&gt;呼叫 API 服務的時候，不是參數錯誤，就是直接報錯找不到（檢查組完的 URL）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這邊也順便記一下怎麼解決的，雖然是用相當暴力的方式：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(a.Replace(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\u200B&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;) == b).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;沒錯，我直接 &lt;code&gt;Replace&lt;/code&gt; 掉它了囧，勉強度過了這次危機。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：如果有更好的處理方式，也歡迎提供給我呦，感謝～&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;雖然我更疑惑的是，資料裡面到底為啥會出現這種東西啦囧……&lt;/p&gt;
&lt;p&gt;最後感謝一下這次讓我得到幫助的網路文章。每次 Debug 都要感謝前人們的禮物，謝謝。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.codenong.com/cs109748163/&#34;&gt;记一个神k，请求地址中加了一堆字符（%E2%80%8B）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://snippetinfo.net/mobile/media/789&#34;&gt;Zero Width Space｜老洪的 IT 學習系統&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2014/01/15/C-Sharp-String-Trim-ZWSP-Zero-width-space&#34;&gt;魔鬼般的細節：使用 C# 的 String.Trim() 方法刪除空白字元 - The Will Will Web&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: BenchmarkDotnet —— 效能測試好簡單</title>
      <link>https://igouist.github.io/post/2021/06/benchmarkdotnet/</link>
      <pubDate>Sun, 13 Jun 2021 22:25:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/06/benchmarkdotnet/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rhmeAUi.webp&#34;
  alt=&#34;&#34;width=&#34;600&#34; height=&#34;475&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;「你寫那什麼鬼東西？這個ＯＯＯ寫法比較好啦！」&lt;br/&gt;
『聽你在屁！明明是這個ＸＸＸ寫法快= =』&lt;/p&gt;
&lt;p&gt;哇喔！等等！&lt;strong&gt;想戰效能嗎&lt;/strong&gt;？那你一定需要這款 &lt;strong&gt;BenchmarkDotnet&lt;/strong&gt;！&lt;/p&gt;
&lt;h2 id=&#34;介紹與安裝&#34;&gt;介紹與安裝&lt;/h2&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://github.com/dotnet/BenchmarkDotNet/raw/master/docs/logo/logo-wide.png&#34;
  alt=&#34;&#34;width=&#34;6970&#34; height=&#34;1235&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們在 Coding 的時候，或多或少都會有「不知道這兩個寫法哪個比較好？」、「聽說Ａ寫法比Ｂ寫法快，真的嗎？」這類關於效能的疑問。&lt;/p&gt;
&lt;p&gt;在遠古時期，當我們需要驗證這種想法，可能就要用記錄秒數的方式，或是搭配迴圈、然後再印在畫面上等等這類土法煉鋼的方式。&lt;/p&gt;
&lt;p&gt;然而這種單純計秒數的 Print 流測試，可能比較到了時間成本，卻忽略了吃掉的記憶體這些空間成本；又或是每次都要插一堆列印文字的語句，因為麻煩就萌生退意等等…&lt;/p&gt;
&lt;p&gt;這時候就是 BenchmarkDotnet 出場的時候啦！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BenchmarkDotnet 是一款簡單好用的效能比較工具，可以幫助我們比對多組程式碼，並告訴我們平均的執行時間、耗用的記憶體等等。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;只要使用 BenchmarkDotnet 這個神奇妙妙幫手，它就能幫我們搞定這些麻煩的事情，讓我們可以專注在要測試的程式碼內容囉。&lt;/p&gt;
&lt;p&gt;接下來要記錄的部份有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#%E4%BB%8B%E7%B4%B9%E8%88%87%E5%AE%89%E8%A3%9D&#34;&gt;介紹與安裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#%E4%BD%BF%E7%94%A8-benchmark-%E4%BE%86%E6%8C%87%E5%AE%9A%E5%8F%83%E8%B3%BD%E9%81%B8%E6%89%8B&#34;&gt;使用 Benchmark 來指定參賽選手&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#%E4%BD%BF%E7%94%A8-memorydiagnoser-%E5%8A%A0%E4%B8%8A%E8%A8%98%E6%86%B6%E9%AB%94%E7%9A%84%E6%AF%94%E8%BC%83&#34;&gt;使用 MemoryDiagnoser 加上記憶體的比較&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#%E4%BD%BF%E7%94%A8-jobs-%E4%BE%86%E6%8C%87%E5%AE%9A%E6%B8%AC%E8%A9%A6%E7%92%B0%E5%A2%83&#34;&gt;使用 Jobs 來指定測試環境&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#%E4%BD%BF%E7%94%A8-exporters-%E4%BE%86%E7%94%A2%E7%94%9F%E5%A0%B1%E8%A1%A8&#34;&gt;使用 Exporters 來產生報表&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#%E7%94%A8-params-%E4%BE%86%E6%8C%87%E5%AE%9A%E6%95%B8%E5%80%BC&#34;&gt;用 Params 來指定數值&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#%E7%B5%90%E8%AA%9E%E8%88%87%E5%8F%83%E8%80%83%E8%B3%87%E6%96%99&#34;&gt;結語與參考資料&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這邊讓我們來實際上跑過一次基本用法並記錄吧！&lt;/p&gt;
&lt;p&gt;首先讓我們先到 Nuget 安裝 &lt;code&gt;BenchmarkDotnet&lt;/code&gt;，因為依賴套件蠻多的，可能需要等待一下。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aqv4KvE.webp&#34;
  alt=&#34;&#34;width=&#34;670&#34; height=&#34;214&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：本篇的專案範本是「主控台應用程式」，不過平常都是直接使用簡單好用的 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10193063&#34;&gt;Linqpad&lt;/a&gt; 快速測一下比較多。&lt;/p&gt;
&lt;p&gt;不過反正 BenchmarkDotnet 已經夠太簡潔方便了，各位朋友用順手的方式試試看就好囉。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;安裝完畢之後，就可以開始準備一下參賽選手的擂台啦～&lt;/p&gt;
&lt;h2 id=&#34;使用-benchmark-來指定參賽選手&#34;&gt;使用 Benchmark 來指定參賽選手&lt;/h2&gt;
&lt;p&gt;既然都說要比較效能了，今天就挑個前陣子同事提到過的主題來比較吧：&lt;/p&gt;
&lt;p&gt;「&lt;strong&gt;在需要回傳空串列的場合，使用 &lt;code&gt;Enumerable.Empty&lt;/code&gt; 會比 &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 更好一些&lt;/strong&gt;」&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;針對這個問題，附上補充資訊：&lt;a href=&#34;https://stackoverflow.com/questions/1894038/is-it-better-to-use-enumerable-emptyt-as-opposed-to-new-listt-to-initial&#34;&gt;Is it better to use Enumerable.Empty&lt;T&gt;() as opposed to new List&lt;T&gt;() to initialize an IEnumerable&lt;T&gt;? - Stackoverflow&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在讓我們用這個主題來測試吧，假定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;紅方選手：用 &lt;code&gt;Enumerable.Empty&lt;/code&gt; 來建立空串列&lt;/li&gt;
&lt;li&gt;藍方選手：用 &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 來建立空串列&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因為我們是要測試兩者做出空串列的差異，至少也要有個類別：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 協力單位，用來當成串列的填充物&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Foo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Guid Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar1 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar2 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar3 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar4 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar5 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後讓我們開始正式的佈置，先讓我們&lt;strong&gt;開一個 Class 來當作擂台&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用擂台&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EmptyVSNewList&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後請紅方選手上擂台：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 紅方選手&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Empty()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Enumerable.Empty&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;以及我們的藍方選手：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 藍方選手&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; NewList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;補充：要直接 &lt;code&gt;Empty() =&amp;gt; Enumerable.Empty&amp;lt;Foo&amp;gt;();&lt;/code&gt; 也是可以的。&lt;/p&gt;
&lt;p&gt;我個人習慣都用同一個準備好的 BenchmarkDotnet 擂台，複製改改裡面的內容就拿來測試了，所以乖乖寫出整個 Function 改起來比較方便 XD&lt;/p&gt;
&lt;p&gt;但為了閱讀方便，本篇後續會整理成比較簡單的 lambda 寫法，請不要太在意。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;選手們就定位之後，&lt;strong&gt;替他們加上 &lt;code&gt;[Benchmark]&lt;/code&gt; 的屬性，作為參賽的證明&lt;/strong&gt;。現在擂台上應該是長這樣的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用擂台&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EmptyVSNewList&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 紅方選手&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Empty()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Enumerable.Empty&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 藍方選手&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; NewList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著，讓我們回到 &lt;code&gt;Main&lt;/code&gt; 方法（或任何你要進行比試的地方），加上：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; summary = BenchmarkRunner.Run&amp;lt;EmptyVSNewList&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;讓 BenchmarkRunner 去抓泛型裡面有 &lt;code&gt;[Benchmark]&lt;/code&gt; 的選手進行測試。&lt;/p&gt;
&lt;p&gt;以我們這次示範的主控台應用程式來說，可能就會像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;namespace&lt;/span&gt; BenchmarkDotnetTest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;[] args)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; summary = BenchmarkRunner.Run&amp;lt;EmptyVSNewList&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用擂台&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EmptyVSNewList&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 紅方選手&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Empty()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Enumerable.Empty&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 藍方選手&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; NewList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 協力單位，用來當成串列的填充物&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Foo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Guid Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar1 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar2 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar3 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar4 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Bar5 { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就安排妥當啦！（其實也就加個測試用的 Class 和讓兩個測試方法而已囧）&lt;/p&gt;
&lt;p&gt;BenchmarkDotnet 必須在 release 環境下啟動，先讓我們切換一下組態：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/iKmHI8C.webp&#34;
  alt=&#34;&#34;width=&#34;152&#34; height=&#34;117&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：如果跟我一樣，喜歡使用 Linqpad 的朋友，右下角切換成 &lt;code&gt;/o+&lt;/code&gt; 才是 Release 組態呦。還有記得要用系統管理員開啟嘿。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在雙方站定，讓我們開始比試吧！&lt;/p&gt;
&lt;p&gt;開始執行之後，會先列一下這次測試的環境資訊，接著就會看到很多輪的比試階段（對，你不用自己寫迴圈來重複測試）：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UN7wXuS.gif&#34;
  alt=&#34;&#34;width=&#34;511&#34; height=&#34;317&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最後就會有測試結果出爐啦：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9UEWxFg.webp&#34;
  alt=&#34;&#34;width=&#34;1124&#34; height=&#34;348&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// * Summary *
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BenchmarkDotNet=v0.13.&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, OS=Windows &lt;span style=&#34;color:#ae81ff&#34;&gt;10.0&lt;/span&gt;.18363.1556 (&lt;span style=&#34;color:#ae81ff&#34;&gt;1909&lt;/span&gt;/November2019Update/19H2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Intel Core i7-&lt;span style=&#34;color:#ae81ff&#34;&gt;7700&lt;/span&gt; CPU &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;.60GHz (Kaby Lake), &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; CPU, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; logical and &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; physical cores
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.NET SDK=&lt;span style=&#34;color:#ae81ff&#34;&gt;5.0&lt;/span&gt;.300
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  [&lt;span style=&#34;color:#66d9ef&#34;&gt;Host&lt;/span&gt;]     &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.1&lt;/span&gt;.15 (CoreCLR &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21202&lt;/span&gt;, CoreFX &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21402&lt;/span&gt;), X64 RyuJIT  [&lt;span style=&#34;color:#66d9ef&#34;&gt;AttachedDebugger&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DefaultJob &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.1&lt;/span&gt;.15 (CoreCLR &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21202&lt;/span&gt;, CoreFX &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21402&lt;/span&gt;), X64 RyuJIT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|  Method |      Mean |     Error |    StdDev |    Median |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|-------- |----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|   Empty | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0016&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0067&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0062&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| NewList | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.6262&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0554&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0491&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.6267&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊可以看到當年統計課熟悉的那些平均值、標準差等等（不過平常戰效能的時候都直接看平均時間比較多啦）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：可以在擂台的類別上（在這個例子中就是 &lt;code&gt;EmptyVSNewList&lt;/code&gt;）加上&lt;br/&gt; &lt;strong&gt;&lt;code&gt;[MinColumn, MaxColumn]&lt;/code&gt;&lt;/strong&gt; 的 Attribute，就會多出最大值和最小值的資訊囉。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|  Method |      Mean |     Error |    StdDev |    Median |       Min |       Max |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|-------- |----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|   Empty | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0005&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0015&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0014&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0054&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| NewList | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.7158&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0490&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0459&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.7072&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.6365&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.8187&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;現在第一階段的結果出爐啦：&lt;code&gt;Enumerable.Empty&lt;/code&gt; 比 &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 快了好幾倍呢！&lt;/p&gt;
&lt;h2 id=&#34;使用-memorydiagnoser-加上記憶體的比較&#34;&gt;使用 MemoryDiagnoser 加上記憶體的比較&lt;/h2&gt;
&lt;p&gt;就像我們前面提到的：效能比較的時候，&lt;strong&gt;除了時間以外，也不能忘了空間&lt;/strong&gt;！也就是說，我們還必須考量到記憶體的用量才可以。&lt;/p&gt;
&lt;p&gt;我們在前一段知道了 &lt;code&gt;Enumerable.Empty&lt;/code&gt; 比 &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 快，但是記憶體的使用呢？現在就讓我們來確認一下吧。&lt;/p&gt;
&lt;p&gt;想要加上記憶體的測試，我們只需要在擂台的類別上，加上 &lt;code&gt;[MemoryDiagnoser]&lt;/code&gt; 就行啦：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用擂台&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[MemoryDiagnoser]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EmptyVSNewList&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Empty() =&amp;gt; Enumerable.Empty&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; NewList() =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們看看結果：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/D1hIAFl.webp&#34;
  alt=&#34;&#34;width=&#34;1131&#34; height=&#34;331&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// * Summary *
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BenchmarkDotNet=v0.13.&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, OS=Windows &lt;span style=&#34;color:#ae81ff&#34;&gt;10.0&lt;/span&gt;.18363.1556 (&lt;span style=&#34;color:#ae81ff&#34;&gt;1909&lt;/span&gt;/November2019Update/19H2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Intel Core i7-&lt;span style=&#34;color:#ae81ff&#34;&gt;7700&lt;/span&gt; CPU &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;.60GHz (Kaby Lake), &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; CPU, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; logical and &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; physical cores
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.NET SDK=&lt;span style=&#34;color:#ae81ff&#34;&gt;5.0&lt;/span&gt;.300
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  [&lt;span style=&#34;color:#66d9ef&#34;&gt;Host&lt;/span&gt;]     &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.1&lt;/span&gt;.15 (CoreCLR &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21202&lt;/span&gt;, CoreFX &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21402&lt;/span&gt;), X64 RyuJIT  [&lt;span style=&#34;color:#66d9ef&#34;&gt;AttachedDebugger&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DefaultJob &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.1&lt;/span&gt;.15 (CoreCLR &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21202&lt;/span&gt;, CoreFX &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21402&lt;/span&gt;), X64 RyuJIT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|  Method |      Mean |     Error |    StdDev |    Median |  Gen &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; | Gen &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; | Gen &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; | Allocated |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|-------- |----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|-------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|   Empty | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0028&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0048&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0040&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns |      - |     - |     - |         - |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| NewList | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.8114&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0877&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1077&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.8019&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0077&lt;/span&gt; |     - |     - |      &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt; B |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以注意到，&lt;code&gt;Empty&lt;/code&gt; 根本沒有動用到記憶體，反而是 &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 占了一些空間，還觸發了 GC。&lt;/p&gt;
&lt;p&gt;由於 &lt;code&gt;Enumerable.Empty&lt;/code&gt; 在時間和空間都拿下了分數，因此這邊宣布：「在需要回傳空串列的場合，使用 &lt;code&gt;Enumerable.Empty&lt;/code&gt; 會比 &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 更好一些」－－&lt;strong&gt;正確&lt;/strong&gt;！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這邊補一下前面提到的 StackOverflow 回答：&lt;br/&gt;
&amp;ldquo;Even if you use an empty array or empty list, those are objects and they are stored in memory. The Garbage Collector has to take care of them.&amp;rdquo; &lt;br/&gt;
&amp;ldquo;Enumerable.Empty does not create an object per call thus putting less load on the GC.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;因此如果當你的查詢有需要返回空串列的時候（例如說搜尋條件沒有結果），試試看用 &lt;code&gt;Enumerable.Empty&lt;/code&gt; 吧！&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;使用-jobs-來指定測試環境&#34;&gt;使用 Jobs 來指定測試環境&lt;/h2&gt;
&lt;p&gt;現在我們已經考量了執行時間和記憶體，接下來要問的就是：&lt;strong&gt;那在不同的啟動環境之下會有差別嗎？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如說同樣的比較，在 .NET Core 跟 .NET framework 都成立嗎？不需要開好幾個專案來測，在 BenchmarkDotnet，我們可以用 &lt;code&gt;Jobs&lt;/code&gt; 來搞定。&lt;/p&gt;
&lt;p&gt;現在讓我們試試看在擂台上掛上對應 .NET Core 的 &lt;code&gt;[SimpleJob(RuntimeMoniker.NetCoreApp30)]&lt;/code&gt; 和 .NET Framework 4.7.2 的 &lt;code&gt;[SimpleJob(RuntimeMoniker.Net472)]&lt;/code&gt; 來試試吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用擂台&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[MemoryDiagnoser]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[SimpleJob(RuntimeMoniker.Net472)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[SimpleJob(RuntimeMoniker.NetCoreApp30)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EmptyVSNewList&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Empty() =&amp;gt; Enumerable.Empty&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; NewList() =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們要編輯專案檔，&lt;strong&gt;用多目標的方式把指定的框架加上去&lt;/strong&gt;，否則直接執行的話會跑出 &lt;code&gt;N/A&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;TargetFrameworks&amp;gt;&lt;/span&gt;netcoreapp3.0;net472&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/TargetFrameworks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;PlatformTarget&amp;gt;&lt;/span&gt;AnyCPU&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/PlatformTarget&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;小提示：專案檔就是 .csproj，除了直接開啟編輯外，也可以從 &lt;code&gt;方案總管 -&amp;gt; (對專案右鍵) -&amp;gt; 編輯專案檔&lt;/code&gt; 來開啟呦。&lt;/p&gt;
&lt;p&gt;此外，多目標的說明和加入方式，也可以參照 Gelis 技術隨筆 的這篇 &lt;a href=&#34;http://gelis-dotnet.blogspot.com/2019/12/targetframeworks-net72-netstandard21.html&#34;&gt;使用多目標 TargetFrameworks 來讓 net72 可參考 .netstandard2.1 通過編譯並可使用&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;以示範專案為例，加入後的 &lt;code&gt;csproj&lt;/code&gt; 檔案的 &lt;code&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/code&gt; 可能會長得這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;TargetFrameworks&amp;gt;&lt;/span&gt;netcoreapp3.0;net472&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/TargetFrameworks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;PlatformTarget&amp;gt;&lt;/span&gt;AnyCPU&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/PlatformTarget&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;小提示：更改版本之後，如果編譯有發生遺失資源檔的錯誤，可以先卸載專案再重新載入試試呦。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在讓我們來執行一次試試吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UqhTEPp.webp&#34;
  alt=&#34;&#34;width=&#34;1446&#34; height=&#34;472&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// * Summary *
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BenchmarkDotNet=v0.13.&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, OS=Windows &lt;span style=&#34;color:#ae81ff&#34;&gt;10.0&lt;/span&gt;.18363.1556 (&lt;span style=&#34;color:#ae81ff&#34;&gt;1909&lt;/span&gt;/November2019Update/19H2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Intel Core i7-&lt;span style=&#34;color:#ae81ff&#34;&gt;7700&lt;/span&gt; CPU &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;.60GHz (Kaby Lake), &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; CPU, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt; logical and &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; physical cores
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.NET SDK=&lt;span style=&#34;color:#ae81ff&#34;&gt;5.0&lt;/span&gt;.300
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  [&lt;span style=&#34;color:#66d9ef&#34;&gt;Host&lt;/span&gt;]               &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.1&lt;/span&gt;.15 (CoreCLR &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21202&lt;/span&gt;, CoreFX &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21402&lt;/span&gt;), X64 RyuJIT  [&lt;span style=&#34;color:#66d9ef&#34;&gt;AttachedDebugger&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.0&lt;/span&gt;        &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.1&lt;/span&gt;.15 (CoreCLR &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21202&lt;/span&gt;, CoreFX &lt;span style=&#34;color:#ae81ff&#34;&gt;4.700&lt;/span&gt;.21.&lt;span style=&#34;color:#ae81ff&#34;&gt;21402&lt;/span&gt;), X64 RyuJIT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .NET Framework &lt;span style=&#34;color:#ae81ff&#34;&gt;4.7&lt;/span&gt;.2 &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; .NET Framework &lt;span style=&#34;color:#ae81ff&#34;&gt;4.8&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;4.8&lt;/span&gt;.4341.&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;), X64 RyuJIT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|  Method |                  Job |              Runtime |      Mean |     Error |    StdDev |    Median |  Gen &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; | Gen &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; | Gen &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; | Allocated |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|-------- |--------------------- |--------------------- |----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|-------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|   Empty |        .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.0&lt;/span&gt; |        .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.0&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0563&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0310&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0583&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0370&lt;/span&gt; ns |      - |     - |     - |         - |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| NewList |        .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.0&lt;/span&gt; |        .NET Core &lt;span style=&#34;color:#ae81ff&#34;&gt;3.0&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.7849&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0734&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0686&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;3.7755&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0077&lt;/span&gt; |     - |     - |      &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt; B |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|   Empty | .NET Framework &lt;span style=&#34;color:#ae81ff&#34;&gt;4.7&lt;/span&gt;.2 | .NET Framework &lt;span style=&#34;color:#ae81ff&#34;&gt;4.7&lt;/span&gt;.2 | &lt;span style=&#34;color:#ae81ff&#34;&gt;2.4641&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0754&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0806&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;2.4815&lt;/span&gt; ns |      - |     - |     - |         - |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| NewList | .NET Framework &lt;span style=&#34;color:#ae81ff&#34;&gt;4.7&lt;/span&gt;.2 | .NET Framework &lt;span style=&#34;color:#ae81ff&#34;&gt;4.7&lt;/span&gt;.2 | &lt;span style=&#34;color:#ae81ff&#34;&gt;7.9914&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.1877&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5139&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;7.8857&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0096&lt;/span&gt; |     - |     - |      &lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt; B |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到測試結果變成了兩組：&lt;strong&gt;在 &lt;code&gt;.NET Core 3.0&lt;/code&gt; 和 &lt;code&gt;.NET Framework 4.7.2&lt;/code&gt; 都進行了測試&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;現在我們知道不管是在 Core 還是 Framework，用 &lt;code&gt;Enumerable.Empty&lt;/code&gt; 來建立空串列都比 &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 快、也更省資源了。&lt;/p&gt;
&lt;h2 id=&#34;使用-exporters-來產生報表&#34;&gt;使用 Exporters 來產生報表&lt;/h2&gt;
&lt;p&gt;現在確定哪一組寫法效能比較好了。想要說服大大改用新寫法，怎麼辦呢？當然是 &lt;del&gt;自己偷偷改&lt;/del&gt; 要拿出證據說服大大啦！&lt;/p&gt;
&lt;p&gt;剛好，BenchmarkDotnet 也提供了產出報表的功能，並且可以輸出成 HTML、Markdown 等格式，只需要加上對應的屬性就可以囉，例如 &lt;strong&gt;&lt;code&gt;[HtmlExporter]&lt;/code&gt;&lt;/strong&gt;、&lt;strong&gt;&lt;code&gt;[CsvExporter]&lt;/code&gt;&lt;/strong&gt;，現在讓我們一股腦都丟上去看看：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用擂台&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HtmlExporter]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[AsciiDocExporter]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[CsvExporter]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[CsvMeasurementsExporter]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[PlainExporter]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[RPlotExporter]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[MemoryDiagnoser]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[SimpleJob(RuntimeMoniker.Net472)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[SimpleJob(RuntimeMoniker.NetCoreApp30)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;EmptyVSNewList&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Empty() =&amp;gt; Enumerable.Empty&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; NewList() =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Foo&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當我們執行的時候，會存一份 Log 在 &lt;code&gt;.\BenchmarkDotNet.Artifacts&lt;/code&gt; 裡面&lt;/p&gt;
&lt;p&gt;而當我們有指定輸出報表，當執行完畢之後，報表就會產生在 Log 的 &lt;code&gt;result&lt;/code&gt;，也就是 &lt;code&gt;.\BenchmarkDotNet.Artifacts\results&lt;/code&gt; 裡面。&lt;/p&gt;
&lt;p&gt;以我的環境為例，就會產生在專案裡的 &lt;code&gt;\bin\Release\netcoreapp3.0\BenchmarkDotNet.Artifacts\results&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/O2VCMNC.webp&#34;
  alt=&#34;&#34;width=&#34;462&#34; height=&#34;203&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其中每個格式的樣子會不太一樣（有點廢話），例如 HTML 的是長這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zCiQ5Js.webp&#34;
  alt=&#34;&#34;width=&#34;1274&#34; height=&#34;317&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而看起來最厲害的應該是用&lt;a href=&#34;https://www.r-project.org/&#34;&gt;Ｒ&lt;/a&gt;的，不過因為我沒有安裝Ｒ，這邊就請大家看一下官網的圖過過癮唄：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://benchmarkdotnet.org/images/v0.12.0/rplot.png&#34;
  alt=&#34;&#34;width=&#34;4236&#34; height=&#34;1874&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;至於報表的詳細操作，大家可以官方介紹的 &lt;a href=&#34;https://benchmarkdotnet.org/articles/configs/exporters.html&#34;&gt;Exporters&lt;/a&gt; 頁面。&lt;/p&gt;
&lt;p&gt;不過我是覺得大多數的場合，光是能拿出執行時間和記憶體用量的比較就已經很有說服力了啦 XD&lt;/p&gt;
&lt;h2 id=&#34;用-params-來指定數值&#34;&gt;用 Params 來指定數值&lt;/h2&gt;
&lt;p&gt;最後補充一下：有時候我們測試的對象，可能也會受到內容物的影響。&lt;/p&gt;
&lt;p&gt;例如一些純計算的方法，可能就會受到數值大小的影響，這時候我們就可以&lt;strong&gt;使用 &lt;code&gt;Params&lt;/code&gt; 的屬性來指定數值&lt;/strong&gt;，並觀察不同狀況下的表現。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ParamsTest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Params(10, 10000)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; A { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Params(2, 20000)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; B { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cul() =&amp;gt; A * B;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣跑出來的結果就會按照指定的數值分組囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| Method |     A |     B |      Mean |     Error |    StdDev | Median |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|------- |------ |------ |----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|----------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|-------&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;|
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|    Cul |    &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; |     &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|    Cul |    &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;20000&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0000&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|    Cul | &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt; |     &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0071&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0146&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0200&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|    Cul | &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;20000&lt;/span&gt; | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0008&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0015&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0023&lt;/span&gt; ns | &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt; ns |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果想要把指定的數值另外拉出來做成串列，比較好維護和測試的話，也可以&lt;strong&gt;用 &lt;code&gt;ParamsSource&lt;/code&gt; 來指定數值的來源&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如上面的例子，也可以改寫成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ParamsTest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&amp;gt; SourceA =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; [] { &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&amp;gt; SourceB =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; [] {  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20000&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [ParamsSource(nameof(SourceA))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; A { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [ParamsSource(nameof(SourceB))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; B { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Benchmark]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cul() =&amp;gt; A * B;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;效果會是一樣的，而且也比較好管理。&lt;/p&gt;
&lt;h2 id=&#34;結語與參考資料&#34;&gt;結語與參考資料&lt;/h2&gt;
&lt;p&gt;我們的 &lt;code&gt;Enumerable.Empty&lt;/code&gt; vs &lt;code&gt;new List&amp;lt;&amp;gt;&lt;/code&gt; 對決也告一段落了。&lt;/p&gt;
&lt;p&gt;當然，BenchmarkDotNet 能做到的事還有很多，例如 &lt;a href=&#34;https://benchmarkdotnet.org/articles/configs/filters.html&#34;&gt;用 Filter 來篩選指定的測試案例&lt;/a&gt;、&lt;a href=&#34;https://benchmarkdotnet.org/articles/features/baselines.html&#34;&gt;把某個測試案例作為基準案例&lt;/a&gt; 等等進階用法，還有更多的 &lt;a href=&#34;https://benchmarkdotnet.org/articles/features/statistics.html&#34;&gt;統計資訊&lt;/a&gt;。但目前還沒有接觸到，這邊就先不提了，有需要的朋友再翻一下參考資料唄。&lt;/p&gt;
&lt;p&gt;這篇文章主要參考自同事提供的範例說明，以及網路大大們的介紹文。特此感謝：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://marcus116.blogspot.com/2019/03/netcore-net-benchmarkdotnet.html&#34;&gt;使用 BenchmarkDotNet 測試程式碼效能&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.kkbruce.net/2017/01/donot-use-for-use-benchmark-dotnet.html#.YIFD8qziuUl&#34;&gt;還在徒手揮汗寫For測效能，閃開讓BenchmarkDotNet來&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotnetcoretutorials.com/2017/12/04/benchmarking-net-core-code-benchmarkdotnet/&#34;&gt;Benchmarking Your .NET Core Code With BenchmarkDotNet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;還有相當完善的官方文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://benchmarkdotnet.org/articles/guides/getting-started.html&#34;&gt;Getting started - benchmarkdotnet.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後，每當你想要問「甘安捏」的時候：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VpNXLeM.webp&#34;
  alt=&#34;&#34;width=&#34;600&#34; height=&#34;464&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用 BenchmarkDotnet 跑一遍就對啦！&lt;/strong&gt; 都給我戰起來！&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧</title>
      <link>https://igouist.github.io/post/2021/05/newbie-4-swagger/</link>
      <pubDate>Sun, 16 May 2021 22:42:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/05/newbie-4-swagger/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lzjNys4.webp&#34;
  alt=&#34;img&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是俺整理公司新訓內容的第四篇文章，目標是&lt;strong&gt;簡單地使用 Swagger 工具來自動產生可互動的 API 文件&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;提醒：還不太認識 API 的朋友，可以先閱讀：&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi/&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt; 呦&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#從-api-規格文件開始&#34;&gt;從 API 規格文件開始&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#什麼是-swagger&#34;&gt;什麼是 Swagger？&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝-swashbuckle-及啟用-swagger-ui&#34;&gt;安裝 Swashbuckle 及啟用 Swagger UI&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-swaggerdoc-增加專案描述&#34;&gt;使用 SwaggerDoc 增加專案描述&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-xml-文件和-includexmlcomments-從註解產生-api-說明&#34;&gt;使用 XML 文件和 IncludeXmlComments 從註解產生 API 說明&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-produces-屬性和-response-註解補充回傳資訊&#34;&gt;使用 Produces 屬性和 response 註解補充回傳資訊&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#相關文章&#34;&gt;相關文章&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#本系列文章&#34;&gt;本系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;從-api-規格文件開始&#34;&gt;從 API 規格文件開始&lt;/h2&gt;
&lt;p&gt;我們介紹 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi/&#34;&gt;API&lt;/a&gt; 的時候有提過：API 是為了讓兩個服務之間可以溝通、互動所產生的接口。而所有的溝通要有效，都一定要先有共識，&lt;strong&gt;隨著溝通的人數越來越多，或是內容的理解要越來越細，就會用文件或契約的方式來達成共識。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;回到我們的 API 服務開發來說，就是你除了把服務生出來了，可以跑了以外，還有一個重要的點是：&lt;strong&gt;必須讓所有的使用者（包含幾個月後的你自己）知道怎麼使用這組 API 服務&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就來說，就是要寫 &lt;strong&gt;API 規格文件&lt;/strong&gt; 啦！&lt;/p&gt;
&lt;p&gt;為了能讓服務對接順利，以及省下大部份口沫橫飛解釋的時間，甚至是讓自己和使用者好幾個月之後能夠順利回想起來，我們在開發 API 的時候一定會列出 API 接口的規格和用法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通常一份 API 文件的內容包括但不限於：用途、路由、參數、回傳值&lt;/strong&gt;，像是參數要放在 Route, QueryString 還是 Body？哪些參數是必填的？回傳的 JSON 範例…等等，都是 API 文件會整理的內容。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;## GET /card/{id}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;**查詢指定編號的卡片**
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;### Parameter
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;-&lt;/span&gt; Route
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`id (int, required)`&lt;/span&gt; 卡片編號
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;example: https://exampleProjN.com/api/card/1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;### Response
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;200: 回傳對應的卡片
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;id&amp;#34;: 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;name&amp;#34;: &amp;#34;string&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;description&amp;#34;: &amp;#34;string&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;attack&amp;#34;: 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;health&amp;#34;: 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;#34;cost&amp;#34;: 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;404: 找不到
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊已經把例子簡化很多了，實際上的 API 文件格式會隨著各地的團隊習慣而改變，用表格、PDF 甚至 Word 之類的狀況也很常見。&lt;/p&gt;
&lt;p&gt;對 API 規格都長怎樣有興趣的朋友，也可以直接在網路上找找一些公開的文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.pixnet.pro/#!/doc/pixnetApi/glossaryArea&#34;&gt;痞客幫 API 文件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.91app.com/developers#api-doc&#34;&gt;91 APP 的開發文件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://agridata.coa.gov.tw/apidocs.aspx&#34;&gt;農業資料開放平台&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://opendata.judicial.gov.tw/news/detail?newsId=3032&#34;&gt;司法院資料開放平台&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不過寫文件畢竟是能登上靠北榜的工作內容之一（靠北榜還包括其他人不寫文件、寫註解、其他人不寫註解等），同時，每次 API 有變動還要一直去維護文件真的很麻煩，所以…&lt;/p&gt;
&lt;p&gt;我們工程師的美德，就是懶惰！ API 文件什麼的，當然是要用自動產生的啦～&lt;/p&gt;
&lt;p&gt;今天要介紹的 Swagger 工具就是幫助我們來自動產生 API 規格文件的好幫手，接下來就先讓我們稍微認識一下 Swagger 吧！&lt;/p&gt;
&lt;h2 id=&#34;什麼是-swagger&#34;&gt;什麼是 Swagger？&lt;/h2&gt;
&lt;p&gt;先上結論：&lt;strong&gt;Swagger 是一套 API 文件產生器，可以幫我們從 API 產生規格文件，更可以建出一個讓使用者能直接呼叫 API 的網頁。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;原本我們說 Swagger 的時候，可能是指 API 文件格式的規範，也有可能是指自動把程式碼轉換成 API 文件的工具。不過 Swagger 在 2015 捐贈給 OpenAPI 之後，一般都用 OpenAPI 來稱呼 API 文件規範了。而 Swagger 更多時候是指 API 文件產生器和相關的生態系，例如像是把 API 文件轉成可互動網頁的 Swagger UI。&lt;/p&gt;
&lt;p&gt;想知道用 Swagger 工具產生的 API 文件長怎樣的朋友，可以到這些地方逛逛按按：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://petstore.swagger.io/&#34;&gt;Swagger 提供的 Demo 網頁（petstore.swagger.io）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tdx.transportdata.tw/api-service/swagger/basic/2998e851-81d0-40f5-b26d-77e2f5ac4118#/&#34;&gt;公車動態的運輸資料服務 TDX (Transport Data eXchange)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;操作上挺直覺的，而且我們前面提到的「用途、路由、參數、回傳」等等 API 資訊都清楚明瞭的顯示，甚至還可以戳一戳，直接呼叫 API 來動手試試。更重要的是：這些都是&lt;strong&gt;自動產生&lt;/strong&gt;的！所以說，Swagger，好！&lt;/p&gt;
&lt;p&gt;也因為 Swagger 是一種工具，所以大多主流語言都會有支援 Swagger 的工具包，例如 Golang 的 swag 和 go-swagger。&lt;/p&gt;
&lt;p&gt;在 Dotnet 陣營裡面，作為代表的則是 &lt;a href=&#34;https://github.com/domaindrivendev/Swashbuckle.AspNetCore&#34;&gt;Swashbuckle&lt;/a&gt; 和 &lt;a href=&#34;https://github.com/RicoSuter/NSwag&#34;&gt;NSwag&lt;/a&gt;。由於工作團隊採用前者，故本篇將會以 &lt;strong&gt;Swashbuckle&lt;/strong&gt; 來逐步實作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;對 NSwag 有興趣，或是工作要求採用的朋友。可以參閱以下資料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10242295&#34;&gt;使用Swagger自動建立清晰明瞭的REST API文件 - 我與 ASP.NET Core 的 30天&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;微軟文件的 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/getting-started-with-nswag?view=aspnetcore-5.0&amp;amp;tabs=visual-studio&#34;&gt;NSwag 與 ASP.NET Core 使用者入門&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;安裝-swashbuckle-及啟用-swagger-ui&#34;&gt;安裝 Swashbuckle 及啟用 Swagger UI&lt;/h2&gt;
&lt;p&gt;現在讓我們把鏡頭回到我們在&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;上一篇&lt;/a&gt;裡使用 .net Core 預設的 Web API 範本建立的簡易 CRUD 服務。&lt;/p&gt;
&lt;p&gt;首先，直奔 Nuget、搜尋 Swashbuckle，應該可以看到一整排：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9vn5HUG.webp&#34;
  alt=&#34;&#34;width=&#34;1013&#34; height=&#34;598&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們這個示範專案的環境是 .net Core，所以我們選擇 &lt;strong&gt;&lt;code&gt;Swashbuckle.AspNetCore&lt;/code&gt;&lt;/strong&gt;，安裝了懶人包，就等於裝好了 Swashbuckle 家的 OpenAPI 三劍客 Swagger、SwaggerGen、SwaggerUI，之後的文件產生和進階操作也就不用煩惱了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;環境不是 .net Core 的 Asp.net Web API 朋友，請安裝 &lt;code&gt;Swashbuckle&lt;/code&gt;，不過整體操作和顯示上並不會相差太多。&lt;/p&gt;
&lt;p&gt;此外，在安裝和操作上也可以參照 mrkt 大大的 Swagger 相關文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/12/aspnet-web-api-swagger.html&#34;&gt;ASP.NET Web API 文件產生器 - 使用 Swagger - mrkt 的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/12/swashbuckle-swagger-for-web-api.html&#34;&gt;Swashbuckle - Swagger for Web Api 顯示內容的調整 - mrkt 的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/En0Aarn.webp&#34;
  alt=&#34;&#34;width=&#34;608&#34; height=&#34;603&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝完成之後，就讓我們來註冊 Swagger 服務吧！&lt;/p&gt;
&lt;p&gt;首先讓我們到 &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;ConfigureServices&lt;/code&gt;，加上 &lt;code&gt;services.AddSwaggerGen();&lt;/code&gt; 把 Swagger 的服務掛上去：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// This method gets called by the runtime.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Use this method to add services to the container.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services.AddSwaggerGen(); &lt;span style=&#34;color:#75715e&#34;&gt;// 註冊 Swagger&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著往下看，到 &lt;code&gt;Configure&lt;/code&gt; 把 Swagger 服務打開，我們需要加上 &lt;code&gt;UseSwagger&lt;/code&gt; 讓它能夠用 middleware 產生 API 文件的 JSON，並用 &lt;code&gt;UseSwaggerUI&lt;/code&gt; 指定 JSON 檔案來產生 API 文件的 UI 頁面。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app.UseSwagger();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app.UseSwaggerUI(c =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c.SwaggerEndpoint(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/swagger/v1/swagger.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;My API V1&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就啟用了 Swagger 的服務了。&lt;/p&gt;
&lt;p&gt;現在讓我們執行偵錯，&lt;strong&gt;並且到專案目錄底下的 &lt;code&gt;/swagger&lt;/code&gt; 路徑&lt;/strong&gt;（以我為例就是 &lt;code&gt;localhost:44304/swagger&lt;/code&gt;），應該就能看到 Swagger 工具的介面啦！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/odrQ5IO.webp&#34;
  alt=&#34;&#34;width=&#34;872&#34; height=&#34;601&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且在上面可以注意到我們&lt;strong&gt;自動生成的 API JSON 文件，也就是前面註冊時看過的 &lt;code&gt;/swagger/v1/swagger.json&lt;/code&gt;&lt;/strong&gt;（以我為例就是 &lt;code&gt;localhost:44304/swagger/v1/swagger.json&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HysAWQu.webp&#34;
  alt=&#34;&#34;width=&#34;871&#34; height=&#34;871&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊大致上就可以了解到，&lt;strong&gt;Swagger 工具就是藉由去掃我們的 &lt;code&gt;ApiController&lt;/code&gt;，產生出對應的 API 規格的 JSON 檔案，再讀取這個 JSON 檔案來產生出 Swagger 的 UI 頁面&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：這邊也可以對 Swagger 的 UI 路徑進行設定，不一定要在 &lt;code&gt;/swagger&lt;/code&gt; 底下。只需要在 &lt;code&gt;UseSwaggerUI&lt;/code&gt; 中使用 &lt;code&gt;RoutePrefix&lt;/code&gt; 就可以指定 Swagger UI 的 Route。&lt;/p&gt;
&lt;p&gt;例如說我想要一進來我們服務的網址，就直接顯示 Swagger 畫面，像是 &lt;code&gt;myapi.com&lt;/code&gt; 就顯示 Swagger UI 而非 &lt;code&gt;myapi.com/swagger&lt;/code&gt; 的時候，就可以這樣設定：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app.UseSwaggerUI(c =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   c.SwaggerEndpoint(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/swagger/v1/swagger.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;My API V1&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   c.RoutePrefix = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Empty; &lt;span style=&#34;color:#75715e&#34;&gt;// 指定路徑為 &amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就會直接在指定的路徑顯示 Swagger UI 囉！&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果有在 &lt;code&gt;Properties/launchSettings.json&lt;/code&gt; 設定偵錯時的起始頁面的朋友，也可以試試&lt;strong&gt;把起始頁面 &lt;code&gt;launchUrl&lt;/code&gt; 設定成 Swagger UI 的路徑&lt;/strong&gt;，例如 &lt;code&gt;&amp;quot;launchUrl&amp;quot;: &amp;quot;swagger&amp;quot;&lt;/code&gt;，平常測試的時候會順手很多，推薦給大家&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著讓我們用 Swagger UI 來測試一下 API 吧，首先讓我們新增一張卡片，選擇新增卡片的 API 並試試 &lt;code&gt;Try it out&lt;/code&gt;，可以看到範例和輸入 Body 的區塊：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8ruN7w9.webp&#34;
  alt=&#34;&#34;width=&#34;911&#34; height=&#34;752&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;執行之後下方就會告訴我們執行結果和回傳：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UUm4v20.webp&#34;
  alt=&#34;&#34;width=&#34;877&#34; height=&#34;790&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著試試用 Swagger UI 來 GET 看看是不是真的有新增成功：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/I5eAVZm.webp&#34;
  alt=&#34;&#34;width=&#34;909&#34; height=&#34;835&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;確認 Swagger UI 的確和我們的 CardController 銜接在一起了，啟用服務成功！&lt;/p&gt;
&lt;h2 id=&#34;使用-swaggerdoc-增加專案描述&#34;&gt;使用 SwaggerDoc 增加專案描述&lt;/h2&gt;
&lt;p&gt;不過現在只是一個可以互動的操作介面而已，離可以取代文件還有一段距離，接著就讓我們一步一步來增加資訊到這個 UI 介面吧。&lt;/p&gt;
&lt;p&gt;首先讓我們回到 &lt;code&gt;ConfigureServices&lt;/code&gt;，修改一下 &lt;code&gt;AddSwaggerGen&lt;/code&gt;，讓我們可以丟東西進去，這邊就直接用微軟文件的範例來稍作修改：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddSwaggerGen(c =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// API 服務簡介&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c.SwaggerDoc(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiInfo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Version = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞 API&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞新訓記的範例 API&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TermsOfService = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Contact = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiContact
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Igouist&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Email = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Empty,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/about/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        License = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiLicense
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TEST&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/about/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊可以填入版本、API 名稱和說明、聯絡方式等資訊，這些資訊會顯示在 Swagger UI 的開頭：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9v4DssU.webp&#34;
  alt=&#34;&#34;width=&#34;501&#34; height=&#34;292&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;替自己的服務加上說明是絕對必要的，不過平時會比較常用的也是 &lt;code&gt;Title&lt;/code&gt;、&lt;code&gt;Description&lt;/code&gt; 這些基本欄位，各位再按照自己的服務調整吧。&lt;/p&gt;
&lt;h2 id=&#34;使用-xml-文件和-includexmlcomments-從註解產生-api-說明&#34;&gt;使用 XML 文件和 IncludeXmlComments 從註解產生 API 說明&lt;/h2&gt;
&lt;p&gt;我們有了整個服務的說明之後，當然也要替每一支 API 補上說明啦！&lt;/p&gt;
&lt;p&gt;這邊我們可以採用&lt;strong&gt;產生 XML 檔案的方式來讓 Swagger 取得每支 API 在 Function 上的註解&lt;/strong&gt;，這樣就能自動產生 API 的說明了。&lt;/p&gt;
&lt;p&gt;首先就是要打開 XML 文件，先讓我們從 &lt;code&gt;方案總管&lt;/code&gt; 對我們的專案 &lt;code&gt;右鍵&lt;/code&gt;，選擇 &lt;code&gt;屬性&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Iol6yxX.webp&#34;
  alt=&#34;&#34;width=&#34;391&#34; height=&#34;931&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;進入屬性頁之後，&lt;strong&gt;到 &lt;code&gt;建置&lt;/code&gt;，找到 &lt;code&gt;XML 文件檔案&lt;/code&gt; 並勾選起來&lt;/strong&gt;，通常會自動幫你填入路徑：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qWU9iJP.webp&#34;
  alt=&#34;&#34;width=&#34;1107&#34; height=&#34;792&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：並非使用 Visual Studio 開發的朋友，也可以參考微軟文件的開啟方式：打開 &lt;code&gt;.csproj&lt;/code&gt; 檔案，並找到 &lt;code&gt;PropertyGroup&lt;/code&gt; 加上 &lt;code&gt;GenerateDocumentationFile&lt;/code&gt; ，並設為 true，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;GenerateDocumentationFile&amp;gt;&lt;/span&gt;true&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/GenerateDocumentationFile&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果開啟 XML 文件檔案的選項後，建置或偵錯時跳出找不到 XML 檔案的錯誤，可能是生成失敗，可以嘗試改用系統管理員開啟 Visual Studio 再重新建置。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;完成並儲存之後，讓我們&lt;strong&gt;回到 &lt;code&gt;ConfigureServices&lt;/code&gt; 的 &lt;code&gt;AddSwaggerGen&lt;/code&gt; 部分，把讀取 XML 的命令也加進去&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlFile = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{Assembly.GetExecutingAssembly().GetName().Name}.xml&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;c.IncludeXmlComments(xmlPath);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果在上一個步驟有加入專案描述，現在的 &lt;code&gt;AddSwaggerGen&lt;/code&gt; 可能會長這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddSwaggerGen(c =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// API 服務簡介&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c.SwaggerDoc(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiInfo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Version = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Title = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞 API&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;菜雞新訓記的範例 API&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TermsOfService = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Contact = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; OpenApiContact
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Igouist&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Email = &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;.Empty,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Url = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Uri(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://igouist.github.io/about/&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 讀取 XML 檔案產生 API 說明&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlFile = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{Assembly.GetExecutingAssembly().GetName().Name}.xml&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c.IncludeXmlComments(xmlPath);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;小提示：這邊的 &lt;code&gt;xmlFile&lt;/code&gt; 路徑是對照前面步驟 專案屬性中的 XML 產生路徑，並用反射的方式去符合自動產生路徑的規則拿到 XML 檔案。&lt;/p&gt;
&lt;p&gt;如果在前面的步驟有自己指定 XML 檔案產生路徑的朋友，這邊的 &lt;code&gt;xmlFile&lt;/code&gt; 也要記得和 XML 檔案路徑對應上呦。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;最後也是最重要的一步：&lt;strong&gt;確保你的 ApiController 底下的各 API 有乖乖加上註解&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yVQQIln.webp&#34;
  alt=&#34;&#34;width=&#34;916&#34; height=&#34;776&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：搭配 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=sergeb.GhostDoc&#34;&gt;GhostDoc&lt;/a&gt; 自動產生註解，又快又香！用過就回不去了。&lt;/p&gt;
&lt;p&gt;相關的說明可以參見：&lt;a href=&#34;https://dotblogs.com.tw/wasichris/2016/01/21/172429&#34;&gt;使用 GhostDoc 自動產出符合語意的註解 - 搞搞就懂&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;確定 &lt;strong&gt;對專案開啟產生 XML、讓 Swagger 讀取 XML、乖乖寫註解&lt;/strong&gt; 三個步驟都有完成之後，就可以開啟 Swagger UI 來看看啦！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/quHdcGV.webp&#34;
  alt=&#34;&#34;width=&#34;392&#34; height=&#34;353&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到每支 API 都有顯示註解的名稱了，讓我們跟沒有的時候比對一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TsroNer.webp&#34;
  alt=&#34;&#34;width=&#34;439&#34; height=&#34;343&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;是不是貼心多了呢？&lt;/p&gt;
&lt;p&gt;並且如果對參數、傳入和傳出的 Model 都有確實加上註解的話，在 API 的內容頁面就可以直接看到 QueryString 的參數，並且對 Model 點選 Schema 也會顯示 Model 的說明：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pI9OJQD.webp&#34;
  alt=&#34;&#34;width=&#34;613&#34; height=&#34;888&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/kPQRL9m.webp&#34;
  alt=&#34;&#34;width=&#34;623&#34; height=&#34;714&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如此一來參數和回傳，從名稱、說明、型別和範例都有了，這樣才有 API 文件的感覺嘛！&lt;/p&gt;
&lt;p&gt;除了基本的 &lt;code&gt;summary&lt;/code&gt; 用來標示 API 的用途、&lt;code&gt;param&lt;/code&gt; 用來標記參數名稱之外，比較特別的就是可以加上 &lt;code&gt;remarks&lt;/code&gt; 來替 API 做更詳細的說明，例如：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/E79TDpY.webp&#34;
  alt=&#34;&#34;width=&#34;481&#34; height=&#34;290&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就會顯示在 Swagger UI 上該 API 點開的內文中：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZFDLWsv.webp&#34;
  alt=&#34;&#34;width=&#34;388&#34; height=&#34;352&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;很適合用來進行更詳細的說明和備註。&lt;/p&gt;
&lt;p&gt;通常我們做到這裡就差不多了已經具備 API 文件該有的部分了，不過資訊當然是多多益善嘛，接著就讓我們來補充一些小東西上去吧～&lt;/p&gt;
&lt;h2 id=&#34;使用-produces-屬性和-response-註解補充回傳資訊&#34;&gt;使用 Produces 屬性和 response 註解補充回傳資訊&lt;/h2&gt;
&lt;p&gt;我們在 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;API 的介紹&lt;/a&gt; 有提到：API 的回傳有許多格式，例如最常見的 JSON、XML，或者是純文字和檔案等等。&lt;/p&gt;
&lt;p&gt;同時，我們也提過 API 可能會根據不同狀況，也會有不同的 HTTP Status 回應，例如 404: 找不到。但讓我們確認一下我們現在的文件…&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IhWDMBe.webp&#34;
  alt=&#34;&#34;width=&#34;649&#34; height=&#34;464&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看起來預設的 Media type 並沒有作用，並且也只有 200 成功時的狀況。現在就讓我們來補充一下吧！&lt;/p&gt;
&lt;p&gt;在目標的 Api 方法上加上 &lt;code&gt;[Produces(&amp;quot;application/json&amp;quot;)]&lt;/code&gt; 就可以標示該方法的回傳格式為 &lt;code&gt;application/json&lt;/code&gt;，例如：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9S1o9sH.webp&#34;
  alt=&#34;&#34;width=&#34;595&#34; height=&#34;326&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣在 Swagger UI 上該 API 的 Responses 就會標記為指定的格式：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OjRt09o.webp&#34;
  alt=&#34;&#34;width=&#34;464&#34; height=&#34;320&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著，讓我們用 &lt;code&gt;ProducesResponseType&lt;/code&gt; 來指定回傳時的型別，以及在註解中使用 &lt;code&gt;&amp;lt;response&amp;gt;&lt;/code&gt; 標籤來替回傳時的 HTTP Status 加上說明吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;remarks&amp;gt;我是附加說明&amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;response code=&amp;#34;200&amp;#34;&amp;gt;回傳對應的卡片&amp;lt;/response&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;response code=&amp;#34;404&amp;#34;&amp;gt;找不到該編號的卡片&amp;lt;/response&amp;gt;          &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Produces(&amp;#34;application/json&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ProducesResponseType(typeof(Card), 200)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card Get([FromRoute] &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Get(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (result &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Response.StatusCode = &lt;span style=&#34;color:#ae81ff&#34;&gt;404&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/I90gVGu.webp&#34;
  alt=&#34;&#34;width=&#34;696&#34; height=&#34;687&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣做的 Swagger UI 就會多出這些 HTTP Status 對應的資訊囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/owvkBq4.webp&#34;
  alt=&#34;&#34;width=&#34;542&#34; height=&#34;605&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;到這邊我們就提供了各種情況對應的回傳啦，隔壁同事如果問你說「為啥我打是回 400 啊？」就可以對他說「&lt;a href=&#34;https://www.urbandictionary.com/define.php?term=RTFW&#34;&gt;RTFW&lt;/a&gt;！」&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2021.07.03 補充：&lt;/p&gt;
&lt;p&gt;我們可以在方法上加上 &lt;strong&gt;&lt;code&gt;[Obsolete]&lt;/code&gt;&lt;/strong&gt; 的已過時屬性，這樣 Swagger 也會用刪除線或反灰的方式告訴使用者該方法已經要被淘汰囉。&lt;/p&gt;
&lt;p&gt;在敝司這次的專案重構，進行 API 接口的翻新和淘汰時，就使用了 &lt;code&gt;[Obsolete]&lt;/code&gt; 來進行標記那些被淘汰的方法和可改用的新方法，挺方便的。&lt;/p&gt;
&lt;p&gt;關於 &lt;code&gt;[Obsolete]&lt;/code&gt;，可以參見 m@rcus 學習筆記的這篇：&lt;a href=&#34;https://marcus116.blogspot.com/2019/10/csharper-method-obsolete.html&#34;&gt;設定方法 (Method) 已過時 - Obsolete&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;這篇記錄了 Swagger 自動產生 API 規格文件的作法，從 API 的路由、說明、參數、Model 跟回傳值全方位說明，還可以試打 API，可以說是&lt;strong&gt;完美提供健全的 API 環境&lt;/strong&gt;啦。&lt;/p&gt;
&lt;p&gt;阿彌陀佛阿彌陀佛，所謂寫一篇文件勝造七級浮屠，這個 Swagger 開下去，還不飛昇當神去了。正是：API 文件寫得好，同事溝通沒煩惱；Swagger 用得好，生文件只要一秒。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/THIt8EI.webp&#34;
  alt=&#34;&#34;width=&#34;687&#34; height=&#34;500&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;那麼今天就記錄到這邊，最後就快速整理一下：&lt;/p&gt;
&lt;p&gt;－ 關於 Swagger －&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;為了提升溝通的效率、確保將來能記得用法，因此我們要寫 API 規格文件&lt;/li&gt;
&lt;li&gt;為了提升寫文件的效率（和懶惰），因此我們會自動產生 API 規格文件&lt;/li&gt;
&lt;li&gt;Swagger 是一套 API 互動文件產生器，是幫助我們來自動產生 API 規格文件和測試的好幫手
&lt;ul&gt;
&lt;li&gt;在 Dotnet 陣營裡以 Swashbuckle 和 NSwag 最常見&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;－ 關於 Swashbuckle －&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 NuGet 安裝 &lt;code&gt;Swashbuckle.Core&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;ConfigureServices&lt;/code&gt; 加上 &lt;code&gt;services.AddSwaggerGen();&lt;/code&gt; 註冊服務&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;Configure&lt;/code&gt; 加上 &lt;code&gt;UseSwagger&lt;/code&gt; 產生 API 文件的 JSON&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;Startup.cs&lt;/code&gt; 的 &lt;code&gt;Configure&lt;/code&gt; 加上 &lt;code&gt;UseSwaggerUI&lt;/code&gt; 來使用 JSON 產生 API 文件的 UI 頁面&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;AddSwaggerGen&lt;/code&gt; 中，使用 &lt;code&gt;SwaggerDoc&lt;/code&gt; 和 &lt;code&gt;OpenApiInfo&lt;/code&gt; 加上服務描述&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;AddSwaggerGen&lt;/code&gt; 中，使用 &lt;code&gt;IncludeXmlComments&lt;/code&gt; 讀取 XML 註解並產生 API 描述（需要先對專案開啟產生 XML 檔案）
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;ApiController&lt;/code&gt; 的各個 API 接口加上 XML 註解，最常見的有
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;summary&lt;/code&gt; 用來標示 API 描述&lt;/li&gt;
&lt;li&gt;&lt;code&gt;param&lt;/code&gt; 用來標示參數描述&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remarks&lt;/code&gt; 用來標示 API 服務的說明&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response&lt;/code&gt; 用來標示回傳的狀態碼說明&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在 API 接口上加上 &lt;code&gt;[Obsolete]&lt;/code&gt; 標示已過時&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;ApiController&lt;/code&gt; 的各個 API 接口加上 &lt;code&gt;[Produces]&lt;/code&gt; 來標記回傳格式&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;ApiController&lt;/code&gt; 的各個 API 接口加上 &lt;code&gt;[ProducesResponseType]&lt;/code&gt; 來標記回傳狀態對應的型別&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今天的紀錄就到這邊，之後如果還有發現什麼小技巧再回來補充，也歡迎幫忙告訴我還能怎麼使用。&lt;/p&gt;
&lt;p&gt;那麼，我們下次見～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;相關文章&#34;&gt;相關文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/swagger-enable-authorize/&#34;&gt;在 Swagger UI 加上驗證按鈕，讓 Request Header 傳遞 Authorize Token&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&amp;amp;tabs=visual-studio&#34;&gt;Swashbuckle 與 ASP.NET Core 使用者入門 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/dotnet-core-webapi%E5%AF%A6%E4%BD%9C%E4%BD%BF%E7%94%A8dapperswaggerpostman-2/&#34;&gt;dotnet Core WebApi實作(使用Dapper、Swagger、Postman)-2 (sunnyday0932.github.io)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/12/aspnet-web-api-swagger.html&#34;&gt;mrkt 的程式學習筆記: ASP.NET Web API 文件產生器 - 使用 Swagger (kevintsengtw.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/12/swashbuckle-swagger-for-web-api.html&#34;&gt;mrkt 的程式學習筆記: Swashbuckle - Swagger for Web Api 顯示內容的調整 (kevintsengtw.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/swagger-notes-1/&#34;&gt;Swagger 初試筆記-黑暗執行緒 (darkthread.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10242295&#34;&gt;使用 Swagger 自動建立清晰明瞭的 REST API文件 - 我與 ASP.NET Core 的 30天 - iT 邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10195190&#34;&gt;ASP.NET Core 2 系列 - Web API 文件產生器 (Swagger)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10230804&#34;&gt;原來後端要知道 - 如何寫 API 文件？ - iT 邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://yunchenli.medium.com/%E4%BD%BF%E7%94%A8swagger%E8%87%AA%E5%8B%95%E7%94%A2%E7%94%9Fapi%E6%96%87%E4%BB%B6-a8f5c65d267c&#34;&gt;使用 Swagger 自動產生 API 文件 | by 11 | Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;本系列文章&#34;&gt;本系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧</title>
      <link>https://igouist.github.io/post/2021/05/newbie-3-dapper/</link>
      <pubDate>Sun, 09 May 2021 11:15:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/05/newbie-3-dapper/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aIHQL5Z.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是俺整理公司新訓內容的第三篇文章，目標是&lt;strong&gt;在 .NET Core 簡單地使用 Dapper 連線到資料庫並完成 CRUD 的功能&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;接續 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;上一篇&lt;/a&gt; 的進度，我們接著要來連線到資料庫中完成我們的 Web Api 的 CRUD 範例。因為從新訓時期到現在工作團隊作業上主要都是使用 Dapper 來做連線資料庫的工作，這邊就直接用 Dapper 來推進吧！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dapper 有多好用呢？它輕量、它簡單、它快速&lt;/strong&gt;。總之先把大神們的介紹文直接拿來鎮樓：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/dapper/&#34;&gt;短小精悍的.NET ORM神器 &amp;ndash; Dapper - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/mrkt/2016/06/10/153606&#34;&gt;另一種資料存取對映處理方式的選擇 - Dapper - mrkt 的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.huanlintalk.com/2014/03/a-micro-orm-dapper.html&#34;&gt;好用的微型 ORM：Dapper - Huanlin 學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那麼按照慣例，我們先來 &lt;del&gt;吹捧今天的主角&lt;/del&gt; 說明一點簡單的前因後果吧。想直接實作的朋友，可以跳到&lt;a href=&#34;#%E6%AD%A3%E5%BC%8F%E9%96%8B%E5%B7%A5&#34;&gt;正式開工&lt;/a&gt;的小節呦。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#前言基本觀念&#34;&gt;前言、基本觀念&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#弱型別與強型別&#34;&gt;弱型別與強型別&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#物件關係對映orm&#34;&gt;物件關係對映（ORM）&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#dapper&#34;&gt;Dapper&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#正式開工&#34;&gt;正式開工&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#環境準備建立資料表安裝-dapper&#34;&gt;環境準備（建立資料表、安裝 Dapper）&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#建立對應資料表的類別&#34;&gt;建立對應資料表的類別&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#使用-dapper-實作-crud&#34;&gt;使用 Dapper 實作 CRUD&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#對接與測試&#34;&gt;對接與測試&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#本系列文章&#34;&gt;本系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;前言基本觀念&#34;&gt;前言、基本觀念&lt;/h2&gt;
&lt;h3 id=&#34;弱型別與強型別&#34;&gt;弱型別與強型別&lt;/h3&gt;
&lt;p&gt;在很久很久以前，從資料庫裏面撈資料庫會使用 &lt;code&gt;DataTable&lt;/code&gt;、&lt;code&gt;DataSet&lt;/code&gt; 的做法去取，但有一個小小的問題，就是這些做法並不是強型別的。當我們在從 &lt;code&gt;Rows[0][&amp;quot;ID&amp;quot;]&lt;/code&gt; 取值的時候，其實我們不知道 Key 對不對，也不知道取不取得到值，更不知道取出來的值是哪個型別。&lt;/p&gt;
&lt;p&gt;當強型別的語法寫慣了之後，再回到上面的這種弱型別環境，就常常會遇到一些令人抓狂的狀況。像是編譯的時候沒出錯，執行的時候才炸掉；因為沒有指定型別，也就無法進行某些操作，必須額外再轉型；轉了型也不知道跑起來是不是和想的一樣等等……&lt;strong&gt;內心充滿了不安感&lt;/strong&gt;。戰戰慄慄，汗不敢出。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;當然，強型別與弱型別各有優缺點，會按照語言環境、個人喜好等等有適用的場合。這邊就不再贅述，對這部分有興趣的朋友可以參照：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2008/01/21/What-is-Strong-Type&#34;&gt;何謂「強型別」(Strong Type) - The Will Will Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10202260&#34;&gt;你不可不知的 JavaScript 二三事#Day3：資料型態的夢魘——動態型別加弱型別&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;至於我們上面提到的，像使用 &lt;code&gt;DataTable&lt;/code&gt; 會掉的坑，可以參考 coreychen71 的這篇 &lt;a href=&#34;https://coreychen71.github.io/posts/2019-06/strongtypeweaktype/&#34;&gt;強型別與弱型別&lt;/a&gt; 的範例，有使用過（踩過）的朋友可能會比較眼熟。&lt;/p&gt;
&lt;p&gt;說到底，想要編譯時期就發現錯誤？或是不想耗費心力在轉型過程？本來就會有相對應的代價。&lt;/p&gt;
&lt;p&gt;不過就我個人認為，既然都在 C# 這個環境了，又是嘗試向已知的資料表取值，取出來的值通常都還會進行進一步的操作，最後還是要指定型別做轉型囧…&lt;/p&gt;
&lt;p&gt;既然你遲早要用強型別的，為什麼不一開始就用強型別呢？還能少一堆坑。&lt;/p&gt;
&lt;p&gt;順便感謝微軟拔拔讓我有 &lt;code&gt;var&lt;/code&gt; 可以用，自動幫我檢查型別又讓我可以爽寫，還不耗效能，讚啦。&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&#34;物件關係對映orm&#34;&gt;物件關係對映（ORM）&lt;/h3&gt;
&lt;p&gt;因為前述的型別問題，大多數人就投向了 ORM 的懷抱。&lt;/p&gt;
&lt;p&gt;ORM 的全名是物件關係對映（Object Relational Mapping），核心理念是將資料庫的資料映射到物件裡，這樣就可以&lt;strong&gt;在我們的程式語言中像直接操作物件一樣地去操作資料&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如說在之前的這篇 &lt;a href=&#34;https://igouist.github.io/post/2019/12/aspnet-connect-db-ef&#34;&gt;Asp.net MVC: Entity Framework 連線資料庫&lt;/a&gt;，就利用了 Entity Framework 這個工具去把資料表和類別對接起來，進而直接在程式碼中對資料進行操作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於 ORM 的更多介紹，例如優點和缺點等等，可以參照這兩篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10207752&#34;&gt;資料庫設計概念 - ORM - johnliutw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.ruanyifeng.com/blog/2019/02/orm-tutorial.html&#34;&gt;ORM 实例教程 - 阮一峰&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;雖然 ORM 帶來了很多方便的好處，例如說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以自動產生 SQL 語法不用自己寫&lt;/li&gt;
&lt;li&gt;包裝之後的語法讓可讀性變好了，操作也變方便了&lt;/li&gt;
&lt;li&gt;資料操作放在專案裡容易維護&lt;/li&gt;
&lt;li&gt;通常都已經做了一些必要的處理，例如用參數的方式幫忙擋了 SQL Injection 啦，使用交易、避免更新衝突等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但畢竟&lt;strong&gt;自動產生語法是有極限的&lt;/strong&gt;，因此 ORM 同時也有著一些問題：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;肥大、前置作業太多&lt;/li&gt;
&lt;li&gt;當語句複雜時轉換成 SQL 的效能可能會變差&lt;/li&gt;
&lt;li&gt;對於較特別或客製化的場景可能會較難處理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最常被提到的坑就是前面提到的「複雜場景產生的 SQL 效能可能會變差」這點；而較難處理的部分，最常見的就是遇到翻寫古老 SQL 程式時，難以將原本的 T-SQL 語法順利轉換成 ORM 語法的狀況，又或者是原本的資料表設計並沒有好好弄好關聯和正規化、早已變得一團亂，導致對已經運行一段時間的專案引入 ORM 就會變得相當困難。&lt;/p&gt;
&lt;p&gt;因此，在使用上需要根據狀況，再決定是否要用 ORM 來進行開發。像是如果對資料表的操作語句單純（例如經典傳統百年不變的 CRUD），或是不需要對 SQL 有太深的認識也要能夠開發，那使用 ORM 就是上上之選。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於各個狀況的比較，這邊推薦黑大的 &lt;a href=&#34;https://blog.darkthread.net/blog/linq-or-direct-sql/&#34;&gt;閒聊：用 LINQ 還是自己寫 SQL？&lt;/a&gt;，整理了在 Dotnet 執行 SQL 邏輯的策略和優缺點，建議可以先看過會比較有概念。&lt;/p&gt;
&lt;p&gt;另外，關於一些 ORM 的問題點，除了上面 ORM 介紹文章中提到的缺點以外，也可以閱讀這篇 &lt;a href=&#34;https://gordon.hk/blog/2019/10/22/You-Dont-need-ORM/&#34;&gt;你不需要 ORM&lt;/a&gt; 有實際的程式碼例子可能會比較有感覺。&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&#34;dapper&#34;&gt;Dapper&lt;/h3&gt;
&lt;p&gt;那既然這樣，我們能不能在上面兩者之間取得一個平衡呢？&lt;/p&gt;
&lt;p&gt;讓一個工具來&lt;strong&gt;協助我們處理和資料庫的溝通&lt;/strong&gt;，幫忙我們&lt;strong&gt;把資料表對應到類別，把資料轉換成物件&lt;/strong&gt;，讓我們可以使用&lt;strong&gt;強型別&lt;/strong&gt;去開發；同時我們又可以保留大部分的彈性，像是&lt;strong&gt;讓我們自己撰寫 SQL 語法&lt;/strong&gt;或一些細微的設定，讓我們可以主動去&lt;strong&gt;調整效能&lt;/strong&gt;？&lt;/p&gt;
&lt;p&gt;如果又比起 ORM 更&lt;strong&gt;輕量&lt;/strong&gt;，又能&lt;strong&gt;快速方便好用&lt;/strong&gt;就好了。&lt;/p&gt;
&lt;p&gt;這種工具真的存在嗎？&lt;/p&gt;
&lt;p&gt;有，就是今天的主角 －－ &lt;strong&gt;Dapper&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;Dapper 是一個輕量的 ORM 工具，&lt;a href=&#34;https://noamlewis.wordpress.com/2012/07/18/net-4-5-improves-orm-performance-across-the-chart/&#34;&gt;效能好&lt;/a&gt;，使用簡單，自由度高。&lt;/p&gt;
&lt;p&gt;它的特色就是&lt;strong&gt;快速、輕便、效能好&lt;/strong&gt;，使用方式也相當簡單，因為它只&lt;strong&gt;幫你處理資料轉物件&lt;/strong&gt;的部份，剩下的像是 SQL 語法和連線，你還是要自己負責。&lt;/p&gt;
&lt;p&gt;不過，我們有讚讚的物件就行了唄，畢竟是物件導向嘛，能當成物件使用最重要了。而且換個方向想，至少我們奪回了 SQL 語法的自主權（？）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充一下：如果你用了 Dapper 但還是很不想寫 SQL，場景又是簡單的 CRUD。呃，&lt;del&gt;為什麼不去用隔壁棚的 EF&lt;/del&gt;，你可以試試加裝 &lt;a href=&#34;https://github.com/tmsmith/Dapper-Extensions&#34;&gt;DapperExtensions&lt;/a&gt; 或 &lt;a href=&#34;https://dotblogs.com.tw/yc421206/2019/03/07/dapper_contrib_insert_update_get&#34;&gt;Dapper.Contrib&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;當我們有了 Dapper 之後…&lt;/p&gt;
&lt;p&gt;把 Dapper 交給那些 認真到會去 SSMS 跑一次 SQL 語法看執行效能的那些朋友，你們可以盡量發揮你們的 SQL 能力取得更好的效能。&lt;/p&gt;
&lt;p&gt;把 Dapper 交給那些 覺得 Entity Framework 效能地雷太多（或只是像我一樣不太熟）的朋友，神秘的事故發生率會少很多。&lt;/p&gt;
&lt;p&gt;把 Dapper 交給那些 覺得還要先建一堆東西做一堆事才能拿資料很麻煩的朋友，快速簡單 Model 開下去 SQL 砸下去就可以爽拿 DB 資料。&lt;/p&gt;
&lt;p&gt;把 Dapper 交給那些 翻寫古老屎山的朋友，移植又臭又長 SQL 語法到新框架的時候，終於可以整個拉過來先跑，再步步為營去重構。&lt;/p&gt;
&lt;p&gt;所以說，Dapper 好！Dapper 妙！Dapper 嚇嚇叫！&lt;/p&gt;
&lt;h2 id=&#34;正式開工&#34;&gt;正式開工&lt;/h2&gt;
&lt;h3 id=&#34;環境準備建立資料表安裝-dapper&#34;&gt;環境準備（建立資料表、安裝 Dapper）&lt;/h3&gt;
&lt;p&gt;到這邊已經用了兩千字來吹捧 Dapper 了，差不多讓我們把鏡頭轉回到 Web Api 服務上，邊推進邊說明一些簡單的用法吧！&lt;/p&gt;
&lt;p&gt;接續我們 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;上一篇&lt;/a&gt; 的進度，在上一篇裡，我們對外開了一組簡單 CRUD 的 API，用來查增修刪我們的卡片資訊。&lt;/p&gt;
&lt;p&gt;但當時只是開了簡單的服務，資料也是暫存的而已。所以這篇的目標是將我們的 API 服務連到資料庫，真正實現對卡片資料的操作。&lt;/p&gt;
&lt;p&gt;先讓我們說明一下本篇的示範環境，假設在 Local 的 SQL Server 裡，有著一個 &lt;code&gt;Newbie&lt;/code&gt; 資料庫，其中有一張 &lt;code&gt;Card&lt;/code&gt; 的資料表。其結構如下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Gec9WeQ.webp&#34;
  alt=&#34;&#34;width=&#34;392&#34; height=&#34;175&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TABLE&lt;/span&gt; [dbo].[Card](
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	[Id] [int] &lt;span style=&#34;color:#66d9ef&#34;&gt;IDENTITY&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	[Name] [nvarchar](&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	[Description] [nvarchar](&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	[Attack] [int] &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	[Health] [int] &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	[Cost] [int] &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;ON&lt;/span&gt; [&lt;span style=&#34;color:#66d9ef&#34;&gt;PRIMARY&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;GO&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;該表用來存放基本的卡牌資訊，包含該卡牌的 ID（主鍵）、卡牌名稱、卡牌描述、攻擊力、血量、花費值。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：使用其他結構的資料表，例如用自己建立的資料表，或是經典的北風資料表，甚至是公司內的資料表來練習開發的朋友，要記得後續的處理都要改成你使用的資料表的內容呦！&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在我們有了一張資料表，鏡頭轉回到我們的 .net Core Web API 服務。&lt;/p&gt;
&lt;p&gt;既然今天的主角是 Dapper，當然要&lt;strong&gt;先打開 NuGet 把 Dapper 給安裝下來&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rT0Nop6.webp&#34;
  alt=&#34;&#34;width=&#34;566&#34; height=&#34;434&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/66GtKJA.webp&#34;
  alt=&#34;&#34;width=&#34;604&#34; height=&#34;182&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/0fuLbQC.webp&#34;
  alt=&#34;&#34;width=&#34;525&#34; height=&#34;344&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;恭喜你，你已經完成本篇文章實作的一半了。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;建立對應資料表的類別&#34;&gt;建立對應資料表的類別&lt;/h3&gt;
&lt;p&gt;使用 Dapper 的時候，我們要先準備好&lt;strong&gt;把資料從拉回來時，用來轉換成物件的類別&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我們在上次已經有在 &lt;code&gt;Models&lt;/code&gt; 資料夾裡建立好 &lt;code&gt;Card.cs&lt;/code&gt;，現在我們就來修改它。（不是從上一期開始跟的朋友，請製作一個和資料表欄位相對應的 Class）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片編號&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片描述&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 攻擊力&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 血量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 花費&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;小提示：如果資料表的欄位數量很多很龐大，手刻會刻到死的朋友，可以試試用 Linqpad 去產生對應的類別。請參閱 mrkt 的這篇 &lt;a href=&#34;https://dotblogs.com.tw/mrkt/2016/06/14/190618&#34;&gt;Dapper - 使用 LINQPad 快速產生相對映 SQL Command 查詢結果的類別&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;用 Linqpad 直接從資料表產生類別，和 Dapper 搭配起來，開發上那可真是一個快啊！這邊推薦給大家。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果建立類別的時候，對於把 SQL Server 裡面的型別對應到 C# 裡有困難的話，可以參閱 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/framework/data/adonet/sql-server-data-type-mappings&#34;&gt;SQL Server 資料類型對應&lt;/a&gt;（感謝 &lt;a href=&#34;https://sunnyday0932.github.io/2020/dotnet-core-webapi%E5%AF%A6%E4%BD%9C%E4%BD%BF%E7%94%A8dapper-1/&#34;&gt;Sian&lt;/a&gt; 的補充）。至於其他家的 DB，呃，還請大家自己查一下囉。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;建立類別的時候要注意，這個類別是用來接收你最後拉回來的資料的。要是你最後打算 Join 兩張表然後各取兩個欄位，這裡的類別就是那兩個欄位；要是你查詢之後只打算返回其中的幾個欄位，這裡的類別就是那幾個欄位。&lt;/p&gt;
&lt;p&gt;在工作上就見識過同事在查詢一張數百欄位的大型表時，針對該表建立了少數欄位、一半欄位、全部欄位三種情況的類別，搭配了泛型和 Dapper 的轉換來接收不同數量的值，藉此控制不同查詢場景時的傳輸成本。因此，建立類別的時候還是要&lt;strong&gt;依照你想要拿到哪些資料下去調整&lt;/strong&gt;比較好。&lt;/p&gt;
&lt;p&gt;刻好要用來接資料的類別之後，接著就讓我們來實作 CRUD 的銜接部分吧！&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;使用-dapper-實作-crud&#34;&gt;使用 Dapper 實作 CRUD&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：你可能需要對 SQL 有基本的認識，至少需要知道對應 CRUD 的 SELECT, INSERT, UPDATE 和 DELETE。不太熟悉的朋友可以參考  &lt;a href=&#34;https://www.1keydata.com/tw/sql/sql.html&#34;&gt;SQL語法教學&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：這個章節會實際操作 Dapper 去資料表取得資料，如果過程中對 Dapper 的各個 Function 使用上有疑惑的話，可以參閱尼克人生的這篇 &lt;a href=&#34;https://dotblogs.com.tw/OldNick/2018/01/15/Dapper#Dapper%20-%20Query&#34;&gt;輕量級ORM - Dapper 使用&lt;/a&gt;，對各方法都有範例，相當好懂，推薦給大家。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在這個步驟，我打算在方案中新增一個 &lt;code&gt;Repository&lt;/code&gt; 資料夾，並在裡面新增 &lt;code&gt;CardRepository.cs&lt;/code&gt; 檔案，用來放我們接下來對資料表的操作。&lt;/p&gt;
&lt;p&gt;這邊的資料夾和檔案命名是因為在公司分層習慣了，所以原本使用 MVC 的朋友，可以把檔案或是以下實作的部分新增在代表資料處理的 &lt;code&gt;Models&lt;/code&gt; 裡就好了，例如加到 &lt;code&gt;Models/Card.cs&lt;/code&gt;，或是放在任何你用來放資料庫連線處理的位置即可，位置並不是本篇記錄的重點。&lt;/p&gt;
&lt;p&gt;現在我們應該會有個空的 Class：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片資料操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardRepository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後讓我們加上連線字串，我的範例直接使用 Localhost 的 DB，所以如果你也有按照步驟來測試的話，這邊請改成你的連線字串。&lt;/p&gt;
&lt;p&gt;連線字串放好之後，回到我們的 &lt;code&gt;CardRepository&lt;/code&gt;，加上放置連線字串的私有常數和建構式：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 連線字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _connectString = &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;Server=(LocalDB)\MSSQLLocalDB;Database=Newbie;Trusted_Connection=True;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊採用直接放私有成員的做法，如果你有多個類別需要用到資料庫連線，可以考慮集中管理連線字串到 &lt;code&gt;appsettings.json&lt;/code&gt; 或是 &lt;code&gt;web.config&lt;/code&gt; 之類的地方。這邊之所以直接放到私有成員裡寫死，是因為這邊的範例情景相對簡單，先不打算挪出去而模糊焦點 &lt;del&gt;，而且我之後要改成依賴注入的時候也比較好改。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;接著就讓我們從查詢全部卡片的方法開始加入吧！我們應該要有一個回傳 卡片串列 的方法（我這邊習慣使用 IEnumerable，比較不熟的朋友用 List 也沒關係）&lt;/p&gt;
&lt;p&gt;準備好了嗎？深呼吸！我們要對資料表進行查詢囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Card&amp;gt; GetList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Query&amp;lt;Card&amp;gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM Card&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就是這麼簡單！&lt;/p&gt;
&lt;p&gt;Dapper 會替實作 IDbConnection 的類別們，也就是我們平常用的連線小幫手們加上擴充方法，讓我們可以方便簡單地使用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：過程中看到紅線請不要慌，如果有需要 using 的就請順手 using 一下，例如 &lt;code&gt;Card&lt;/code&gt; 的類別、等等會用到的 &lt;code&gt;Dapper&lt;/code&gt; 等等。或是像 &lt;code&gt;SqlConnection&lt;/code&gt; 沒抓到套件，就 Alt Enter 安裝一下即可。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：因為 Dapper 對每個資料庫系統所產生的語法會不一樣，所以假如你是使用 MySql 的朋友，這邊的連線請改成使用 &lt;code&gt;MySqlConnection&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;如果你的 C# 版本大於 8.0 ，using 語句甚至不需要大括弧，如果又不像我喜歡把每一小段都宣告成變數來加減表達意圖的話，整體就更簡潔了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Card&amp;gt; GetList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; conn.Query&amp;lt;Card&amp;gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM Card&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊也能看到 Dapper 最常見的使用方式：&lt;strong&gt;當我們查詢的時候，可以使用 &lt;code&gt;Query&amp;lt;T&amp;gt;&lt;/code&gt; 方法，並將我們要接收資料的類別放入泛型，再將我們的 SQL 語法做為參數傳入。Dapper 會執行 SQL 並嘗試把查詢結果轉換成我們指定的類別。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：Dapper 會去對照資料表的欄位名稱和類別中的欄位名稱，名稱一樣且型別能對應的就會把資料放進去。&lt;/p&gt;
&lt;p&gt;如果遇到兩者有不一樣的，例如資料表欄位開成 &lt;code&gt;card_name&lt;/code&gt; 但是類別中的欄位是 &lt;code&gt;Name&lt;/code&gt; 這種時候，比較簡單暴力的做法是在 &lt;code&gt;SELECT&lt;/code&gt; 的時候用 &lt;code&gt;AS&lt;/code&gt; 替欄位命名，或是告訴 Dapper 指定的欄位對照，有這個需求的朋友請參照軟體主廚的這篇 &lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/08/16/175753&#34;&gt;料理佳餚 - Dapper 自定義欄位對應的三種方式&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果有好奇的朋友也可以嘗試不給 &lt;code&gt;Query&amp;lt;T&amp;gt;&lt;/code&gt; 指定型別，這樣回來的就會是 &lt;code&gt;dynamic&lt;/code&gt;，但是不太建議這樣用啦，畢竟我們就是打算要享受強型別的好處才這樣做的嘛！何必又回到方法不能用、執行才報錯的時代呢？乖乖建立類別還比較實在。這部分請參見 &lt;a href=&#34;https://dotblogs.com.tw/mrkt/2016/06/12/170411&#34;&gt;每個查詢的結果都要定義並對映一個類別嗎？（使用 dynamic）&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;事不宜遲，我們接著建立查詢單筆的方法吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.QueryFirstOrDefault&amp;lt;Card&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT TOP 1 * FROM Card Where Id = @id&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            { 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Id = id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;從這段我們可以知道兩件事情：&lt;/p&gt;
&lt;p&gt;除了 &lt;code&gt;Query&lt;/code&gt; 之外，&lt;strong&gt;Dapper 還準備了一些針對不同查詢的 &lt;code&gt;Query&lt;/code&gt; 方法給大家使用&lt;/strong&gt;，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QueryFirst&lt;/code&gt; 只取第一筆，如果找不到就報錯&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QueryFirstOrDefault&lt;/code&gt; 只取第一筆，如果找不到就丟回預設值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QuerySingle&lt;/code&gt;，只取一筆，如果找不到或是找到多筆符合的就報錯&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QuerySingleOrDefault&lt;/code&gt; 只取一筆，如果找不到就丟回預設值，找到多筆符合的就報錯&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除此之外，如果你的架構已經大量使用非同步，Dapper 也都有提供非同步版本的方法使用，例如 &lt;code&gt;QueryAsync&lt;/code&gt;，這邊的示範專案還在黑暗時代，就不贅述。&lt;/p&gt;
&lt;p&gt;至於 &lt;code&gt;Query&lt;/code&gt; 系列裡面比較特別的應該就是 &lt;code&gt;QueryMultiple&lt;/code&gt; 了，它能允許同時執行多段 SQL，例如 &lt;code&gt;SELECT * FROM Card; SELECT TOP 1 * FROM Card;&lt;/code&gt; 之類的，並取回一串結果。&lt;/p&gt;
&lt;p&gt;取得之後再逐一用 &lt;code&gt;Read&amp;lt;T&amp;gt;()&lt;/code&gt; 的方式來把結果轉換成物件，但由於平常開 Function 會需要考慮 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;單一職責原則&lt;/a&gt; 的關係，很少接觸到有需要在同個方法執行兩段 SQL 並回傳兩個甚至多個不同物件的狀況，這邊就留給有需要的朋友自己嘗試囉。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;回到我們剛剛開好的查詢卡片方法，第二件事就是我們知道了 &lt;strong&gt;&lt;code&gt;Query&amp;lt;T&amp;gt;&lt;/code&gt; 系列可以傳入第二個參數，用來告訴 Dapper 這次 SQL 語法會用到的變數&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;只要丟個物件進去，就算是匿名物件，Dapper 也會幫忙做成 SQL 的參數。有些朋友可能會問：為什麼不直接用 &lt;code&gt;+&lt;/code&gt; 的方式或是直接 &lt;a href=&#34;https://igouist.github.io/post/2020/08/csharp-string-interpolation&#34;&gt;字串插值&lt;/a&gt; 組到 SQL 裡面就好了呢？&lt;/p&gt;
&lt;p&gt;如果真的有這個問題的話，呃，&lt;code&gt;SQL Injection&lt;/code&gt; 先了解一下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;給那些想瞭解又懶得跳出去查的朋友：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E7%A8%8B%E5%BC%8F%E7%8C%BF%E5%90%83%E9%A6%99%E8%95%89/%E6%B7%BA%E8%AB%87%E9%A7%AD%E5%AE%A2%E6%94%BB%E6%93%8A-%E7%B6%B2%E7%AB%99%E5%AE%89%E5%85%A8-%E4%B8%80%E6%AC%A1%E7%9C%8B%E6%87%82-sql-injection-%E7%9A%84%E6%94%BB%E6%93%8A%E5%8E%9F%E7%90%86-b1994fd2392a&#34;&gt;網站安全🔒 一次看懂 SQL Injection 的攻擊原理 — 「雍正繼位之謎」&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10189201&#34;&gt;攻擊行為－SQL 資料隱碼攻擊 SQL injection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;簡單來說，為了防範不良分子在 SQL 上「自由發揮」，通常都不會允許直接把傳進來的參數等等直接丟到 SQL 裡面去串起來。&lt;/p&gt;
&lt;p&gt;所以現在比較常見也被認為是最有效的作法是，把參數另外宣告成 SQL 提供的變數，例如 Sql Server 的 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/sql/t-sql/language-elements/variables-transact-sql?view=sql-server-ver15&#34;&gt;變數&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;如此一來，資料庫就會先解析完 SQL 語法，才嘗試把變數「作為字串」塞進去指定的位置。藉此來防範 SQL Injection 的問題。&lt;/p&gt;
&lt;p&gt;同樣是 &lt;code&gt;SELECT * FROM Card WHERE id = {id};&lt;/code&gt;，然後傳入一樣是 &lt;code&gt;1; DROP TABLE Card&lt;/code&gt;;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直接銜接：&lt;code&gt;SELECT * FROM Card WHERE id = 1; DROP TABLE Card;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;作為字串：&lt;code&gt;SELECT * FROM Card WHERE id = &amp;quot;1; DROP TABLE Card&amp;quot;;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這樣子的差異應該就能理解為什麼參數化查詢能夠防範 SQL Injection 了，因為通通給你包起來。除此之外，參數化查詢還有能夠重複使用執行計畫提升效能、好維護等等好處，真的是屌打字串拼接黨。&lt;/p&gt;
&lt;p&gt;回到我們剛剛開好的查詢卡片方法，在這邊我們必須用卡片 ID 來查詢卡片，所以必須將 ID 丟進去。那就可以看到我們的 SQL 條件有加上 &lt;code&gt;Where Id = @Id&lt;/code&gt;，告訴 SQL 我們有一個 &lt;code&gt;@Id&lt;/code&gt; 變數，接著我們再把 ID 包成物件丟給 Dapper 請他幫忙處理一下。&lt;/p&gt;
&lt;p&gt;不過我個人比較不喜歡把東西都集中一坨在方法的呼叫上，所以我會把 SQL 和參數都拆分出來，並使用 &lt;strong&gt;DynamicParameters&lt;/strong&gt; 來建立參數，如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        SELECT * 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        FROM Card 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        Where Id = @id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.QueryFirstOrDefault&amp;lt;Card&amp;gt;(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣切出來做成變數也比較整潔美觀好清理，如果有需要按照狀況增加 SQL 語法或參數的時候，也比較方便。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 假設查詢卡片列表的時候，為了知道這回合能用的卡片有哪些&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 需要指定卡片的「花費值」必須低於多少，所以多了一個參數 int? cost 的場合：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (cost != &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &amp;amp;&amp;amp; cost &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sql += &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; And Cost &amp;lt;= @Cost &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Cost&amp;#34;&lt;/span&gt;, cost);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當然上面這段只是示範，實務上在附加條件和參數的時候也會遇到…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因為不想判斷前面有沒有 &lt;code&gt;WHERE&lt;/code&gt; 語句，所以把所有條件先放到陣列中，最後再用 &lt;code&gt;String.Join&lt;/code&gt; 以 &lt;code&gt;AND&lt;/code&gt; 連接起來的&lt;/li&gt;
&lt;li&gt;同上，但比較古老的時代會使用 &lt;code&gt;WHERE 1 = 1&lt;/code&gt; 然後再接 &lt;code&gt;AND&lt;/code&gt; 串條件&lt;/li&gt;
&lt;li&gt;由於參數太多，因為效能上的考量，所以不用 &lt;code&gt;+=&lt;/code&gt; 來連接 SQL 語句，而是使用 &lt;code&gt;StringBuilder&lt;/code&gt; 的&lt;/li&gt;
&lt;li&gt;把 SQL 語法內共用的部分拆出去做成 Private 以提高程式碼共用程度，減少重複贅詞的&lt;/li&gt;
&lt;li&gt;直接把整個 SQL 語法拆出去放別的地方的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;等等各種因應狀況所採取的手段，各位在處理這部分的時候，如果不是自己新建的專案，還請觀察一下團隊的用法再自行調整。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;除此之外，這邊還有一個小細節需要補充：&lt;strong&gt;當我們在使用 &lt;code&gt;DynamicParameters&lt;/code&gt;，把變數丟進去時，Dapper 會自動幫我們做型別上的轉換&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;BUT！就是這個 BUT！對於一些 &lt;code&gt;int&lt;/code&gt; 啦、&lt;code&gt;bool&lt;/code&gt; 的是不會有什麼問題，但一些比較難對應得到的型別，Dapper 就會嘗試用比較穩的打法，例如 &lt;code&gt;String&lt;/code&gt; 就會變成 &lt;code&gt;nvarchar(4000)&lt;/code&gt; 之類的。&lt;/p&gt;
&lt;p&gt;但是，型別不同對 SQL 的執行計畫也會造成影響，甚至會造成效能變差。針對這個問題，我們可以在加入參數的時候，一併&lt;strong&gt;告訴 Dapper 我們指定的型別&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如前述的 &lt;code&gt;parameters.Add(&amp;quot;Id&amp;quot;, id)&lt;/code&gt;，可以給第三個參數告訴它 DBType，變成 &lt;code&gt;parameters.Add(&amp;quot;Id&amp;quot;, id, System.Data.DbType.Int32);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;關於這個型別調整的部分，有興趣的朋友可以閱讀軟體主廚的這篇：&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2019/08/12/232213&#34;&gt;Dapper 用起來很友善，但是預設的參數型別對執行計劃不太友善&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;裡面針對型別影響執行計畫有進行測試，並且提供了針對 DbString 更方便使用的 String 擴充方法，我們團隊也有將其應用在專案上，推薦大家可以瞭解一下。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;現在我們已經解決了查詢，接著讓我們來處理一下新增卡片和修改卡片的部分吧！&lt;/p&gt;
&lt;p&gt;首先先讓我們回到上篇的 &lt;code&gt;CardParameter&lt;/code&gt;，把這次多的欄位也給補上。如果是這篇才加入的朋友，請自己捏一個 &lt;code&gt;CardParameter.cs&lt;/code&gt; 出來，我們在新增和修改的時候會拿來當作參數使用。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片參數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片描述&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 攻擊力&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 血量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 花費&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果在業務上有些需求導致新增和修改的參數不一樣的朋友（例如有些欄位不開放修改），也可以考慮拆分成兩個 Parameter 去面對不同的場景。&lt;/p&gt;
&lt;p&gt;補上之後讓我們回到 &lt;code&gt;CardRepository&lt;/code&gt;，利用 SQL 的 &lt;code&gt;INSERT&lt;/code&gt; 和 &lt;code&gt;UPDATE&lt;/code&gt; 語法來把 &lt;code&gt;Parameter&lt;/code&gt; 的內容給塞進去吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Create(CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        INSERT INTO Card 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            [Name]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,[Description]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,[Attack]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,[Health]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,[Cost]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        ) 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        VALUES 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            @Name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,@Description
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,@Attack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,@Health
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;           ,@Cost
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        SELECT @@IDENTITY;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.QueryFirstOrDefault&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&amp;gt;(sql, parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 修改卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        UPDATE Card
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        SET 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;             [Name] = @Name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            ,[Description] = @Description
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            ,[Attack] = @Attack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            ,[Health] = @Health
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            ,[Cost] = @Cost
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        WHERE 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            Id = @id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Execute(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊可以注意到，&lt;strong&gt;除了查詢用的 &lt;code&gt;Query&lt;/code&gt; 以外，Dapper 也提供了執行指令用的 &lt;code&gt;Execute&lt;/code&gt;&lt;/strong&gt;。在一些不需要回傳東西的時候，例如更新和刪除，又或是單純呼叫預存程序（SP, Stored Procedure）的時候相當方便，而且當然也有提供非同步的版本可以使用。&lt;/p&gt;
&lt;p&gt;而最強大的地方是，&lt;strong&gt;Dapper 支援多筆新增和更新&lt;/strong&gt;。例如前面我們的新增卡片 &lt;code&gt;Create(CardParameter parameter)&lt;/code&gt; 裡面：&lt;/p&gt;
&lt;p&gt;如果我們是直接改成丟一整串的新卡片進來，也就是 &lt;code&gt;Create(IEnumerable&amp;lt;CardParameter&amp;gt; parameters)&lt;/code&gt; 然後呼叫 Dapper 來跑 &lt;code&gt;conn.Execute(sql, parameters)&lt;/code&gt; 是可以新增多筆卡片的，更新也是同樣的道理。&lt;/p&gt;
&lt;p&gt;同時也因為可以執行多個 SQL 語句、新增多筆資料，&lt;strong&gt;Dapper 也提供了交易（Transaction）&lt;/strong&gt;，只需要 &lt;code&gt;using(var transaction = conn.BeginTransaction())&lt;/code&gt;，完成後 &lt;code&gt;transaction.Commit()&lt;/code&gt; 就可以囉。不過這些部份我們暫時不會用到，有興趣的朋友可以再自己嘗試看看。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：由於我個人習慣新增資料之後，用 &lt;code&gt;@@IDENTITY&lt;/code&gt; 或 &lt;code&gt;LAST_INSERT_ID()&lt;/code&gt; 這類語法把該筆資料的 ID 拉回來方便後續檢查和使用，所以在 &lt;code&gt;Create()&lt;/code&gt; 是使用 &lt;code&gt;Query&amp;lt;int&amp;gt;&lt;/code&gt; 的方式來取得 ID。沒有這類需求的朋友，也可以像 &lt;code&gt;Update()&lt;/code&gt; 的部分一樣用 &lt;code&gt;Execute&lt;/code&gt; 就可以了。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;到這邊大家應該已經大致了解 Dapper 的使用方式了，讓我們把最後的刪除卡片補上去吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        DELETE FROM Card
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        WHERE Id = @Id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Execute(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到這邊我們就做完 CRUD 一套囉！現在的 &lt;code&gt;CardRepository&lt;/code&gt; 應該會長得像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardRepository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 連線字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _connectString = 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;Server=(LocalDB)\MSSQLLocalDB;Database=Newbie;Trusted_Connection=True;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Card&amp;gt; GetList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM Card&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Query&amp;lt;Card&amp;gt;(sql);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card Get(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            SELECT * 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            FROM Card 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            Where Id = @id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.QueryFirstOrDefault&amp;lt;Card&amp;gt;(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Create(CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            INSERT INTO Card 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;               [Name]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              ,[Description]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              ,[Attack]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              ,[Health]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              ,[Cost]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            ) 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            VALUES 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                 @Name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,@Description
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,@Attack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,@Health
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,@Cost
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            SELECT @@IDENTITY;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.QueryFirstOrDefault&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&amp;gt;(sql, parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 修改卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; Update(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id, CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            UPDATE Card
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            SET 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                 [Name] = @Name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,[Description] = @Description
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,[Attack] = @Attack
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,[Health] = @Health
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ,[Cost] = @Cost
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            WHERE 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                Id = @id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Execute(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Delete(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; sql =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            DELETE FROM Card
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            WHERE Id = @Id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; parameters = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DynamicParameters();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        parameters.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;, id, System.Data.DbType.Int32);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; conn = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(_connectString))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = conn.Execute(sql, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id=&#34;對接與測試&#34;&gt;對接與測試&lt;/h3&gt;
&lt;p&gt;接著就讓我們回到 &lt;code&gt;CardController&lt;/code&gt; 把這邊的操作和 API 對外的開口給銜接起來。&lt;/p&gt;
&lt;p&gt;首先先把 &lt;code&gt;CardRepository&lt;/code&gt; 宣告成私有成員，取代掉我們原本用來暫存卡片資料的 &lt;code&gt;private static List&amp;lt;Card&amp;gt; _cards&lt;/code&gt;。並且在建構式進行賦值（&lt;del&gt;一樣是打算讓之後改成注入的時候比較好改XD&lt;/del&gt;）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片資料操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; CardRepository _cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardController()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;後續其實就是把 &lt;code&gt;_cards&lt;/code&gt; 給砍掉後，並且把各個操作改成呼叫 &lt;code&gt;CardRepository&lt;/code&gt; 對應的方法，如果是這篇才加入的朋友，也就直接建立各個方法去對接 &lt;code&gt;CardRepository&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;過程就不再贅述。修改後應該會長這個樣子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片資料操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; CardRepository _cardRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; CardController()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardRepository();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;Card&amp;gt; GetList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.GetList();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card Get([FromRoute] &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Get(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (result &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Response.StatusCode = &lt;span style=&#34;color:#ae81ff&#34;&gt;404&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert([FromBody] CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Create(parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (result &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPut]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Update(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; targetCard = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Get(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (targetCard &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; NotFound();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isUpdateSuccess = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Update(id, parameter);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isUpdateSuccess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; StatusCode(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpDelete]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Delete([FromRoute] &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._cardRepository.Delete(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就和上一篇一樣，讓我們按照 新增 → 查詢列表 → 修改 → 查詢單筆 → 刪除 的順序來跑一次看看吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：因為這邊還沒說明到 Postman 等測試軟體，所以直接使用 Powershell 呼叫 API 進行示範。已經有慣用軟體的朋友，請用自己方便順手的測試方法去呼叫就好囉。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先讓我們新增一張卡片（記得 URL 和 Port 要改成你啟動的版本呦）：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Invoke-RestMethod https://localhost:44304/card `
 -Method &amp;#39;POST&amp;#39; `
 -Headers @{ &amp;#34;Content-Type&amp;#34; = &amp;#34;application/json&amp;#34;; } `
 -Body &amp;#34;{`&amp;#34;name`&amp;#34;: `&amp;#34;mycard`&amp;#34;,`&amp;#34;description`&amp;#34;: `&amp;#34;sample card`&amp;#34;, `&amp;#34;attack`&amp;#34;: 3, `&amp;#34;health`&amp;#34;: 4, `&amp;#34;cost`&amp;#34;: 2 }&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/oQOv7VN.webp&#34;
  alt=&#34;&#34;width=&#34;1162&#34; height=&#34;308&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後讓我們在 SSMS（SQL Server Management Studio）確認一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CuxT7jB.webp&#34;
  alt=&#34;&#34;width=&#34;311&#34; height=&#34;77&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們試試查詢：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Invoke-RestMethod https://localhost:44304/card | ConvertTo-Json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XmKRKsm.webp&#34;
  alt=&#34;&#34;width=&#34;758&#34; height=&#34;459&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;既然查詢成功了，就針對這筆來試試看修改：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Invoke-RestMethod https://localhost:44304/card/1 `
 -Method &amp;#39;PUT&amp;#39; `
 -Headers @{ &amp;#34;Content-Type&amp;#34; = &amp;#34;application/json&amp;#34;; } `
 -Body &amp;#34;{`&amp;#34;name`&amp;#34;: `&amp;#34;ourcard`&amp;#34;,`&amp;#34;description`&amp;#34;: `&amp;#34;sample card`&amp;#34;, `&amp;#34;attack`&amp;#34;: 4, `&amp;#34;health`&amp;#34;: 5, `&amp;#34;cost`&amp;#34;: 3}&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Xo5JFyP.webp&#34;
  alt=&#34;&#34;width=&#34;1170&#34; height=&#34;270&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;一樣在 SSMS 確認一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SxsQdc2.webp&#34;
  alt=&#34;&#34;width=&#34;314&#34; height=&#34;81&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;看來我們有成功更新到，讓我們針對這筆來查詢看看：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Invoke-RestMethod https://localhost:44304/card/1 | ConvertTo-Json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/iieIxee.webp&#34;
  alt=&#34;&#34;width=&#34;736&#34; height=&#34;344&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;查詢出來的也的確是變動過的結果了。最後，讓我們把這張卡片刪除，回歸乾淨吧！&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Invoke-RestMethod https://localhost:44304/card/1 `
 -Method &amp;#39;DELETE&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WcNVRKA.webp&#34;
  alt=&#34;&#34;width=&#34;547&#34; height=&#34;105&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;確認資料表的卡片已經消失：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/a5toUnV.webp&#34;
  alt=&#34;&#34;width=&#34;307&#34; height=&#34;87&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;到這邊我們就宣告完工啦！&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;我們把前篇的 Web API 服務利用 Dapper 連接到 SQL Server，並成功完成了基本的 CRUD 功能。&lt;/p&gt;
&lt;p&gt;這邊總結一下這篇的一些小要點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DataTable&lt;/code&gt; 等弱型別在使用上會有偵錯困難、必須額外轉型等問題；而直接對映的 &lt;code&gt;ORM&lt;/code&gt; 又常有過於肥大、自動產生的語法效能可能不佳的問題&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Dapper&lt;/code&gt; 是一款輕量級的 ORM&lt;/strong&gt;，它具有以下特色：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;簡單&lt;/strong&gt;：能幫我們將資料表欄位對應到類別欄位，讓我們查詢的資料能簡單直接地轉換成物件，享受強型別的好處&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;輕便&lt;/strong&gt;：比起其他 ORM，相當輕便，引入套件後就能快速開始使用，效能也相當不錯&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;彈性&lt;/strong&gt;：可以自己撰寫 SQL 語法，自己進行效能的優化與調整，相對 ORM 提升了自由度和彈性，但也要自己對語法負責&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用 &lt;strong&gt;&lt;code&gt;Query&lt;/code&gt; 系列的方法來進行資料的查詢&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;提供了 &lt;code&gt;QueryFirstOrDefault&lt;/code&gt; 等方法來進一步調整查詢方式（&lt;a href=&#34;https://dotblogs.com.tw/OldNick/2018/01/15/Dapper#Dapper%20-%20Query&#34;&gt;參閱&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;提供了 &lt;code&gt;Async&lt;/code&gt; 結尾的非同步方法&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;Query&amp;lt;T&amp;gt;&lt;/code&gt; 中指定對應資料表欄位的類別來讓 Dapper 進行轉換&lt;/li&gt;
&lt;li&gt;建立對應資料表欄位的類別時，可以用 Linqpad 來節省時間（&lt;a href=&#34;https://dotblogs.com.tw/mrkt/2016/06/14/190618&#34;&gt;參閱&lt;/a&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用 &lt;strong&gt;&lt;code&gt;Execute&lt;/code&gt; 系列的方法來進行語法的執行&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;同樣提供了 &lt;code&gt;Async&lt;/code&gt; 結尾的非同步方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用 &lt;strong&gt;&lt;code&gt;DynamicParameters&lt;/code&gt; 來建立參數集合&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;藉由參數化查詢迴避 SQL Injection 攻擊&lt;/li&gt;
&lt;li&gt;預設的型別轉換可能會有效能問題，在使用 DBString 等難以對應的型別時，可以自訂型別轉換來提升效能（&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2019/08/12/232213&#34;&gt;參閱&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;允許傳遞多組參數來執行同一段 SQL 語法，藉此可以做到多筆新增、多筆更新的效果&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;提供了 &lt;code&gt;BeginTransaction&lt;/code&gt; 來開啟一段 SQL Transaction&lt;/li&gt;
&lt;li&gt;針對不同的查詢場景、邏輯的複雜程度等等，我們應該評估後再決定使用傳統 ORM 或是 Dapper 或其他方式來操作資料庫（&lt;a href=&#34;https://blog.darkthread.net/blog/linq-or-direct-sql/&#34;&gt;參閱&lt;/a&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今天就到這邊告一段落。現在這組 Web API 已經是常見的「連接到資料庫進行基本操作」的範本了，接下來我們會針對這組 Web API 服務進行各式各樣的 &lt;del&gt;擺弄&lt;/del&gt; 改造。&lt;/p&gt;
&lt;p&gt;那麼，我們下次見～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生簡單好看可測試的 API 文件吧&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;本系列文章&#34;&gt;本系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (2): 認識 Api &amp; 使用 .net Core 來建立簡單的 Web Api 服務吧</title>
      <link>https://igouist.github.io/post/2021/05/newbie-2-webapi/</link>
      <pubDate>Sun, 02 May 2021 12:39:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/05/newbie-2-webapi/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/d2xM94x.webp&#34;
  alt=&#34;Image&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是俺整理公司新訓內容的第二篇文章，目標是&lt;strong&gt;對 Api, Restful Api, HTTP 等相關的知識點做個筆記，並用 .net Core 建立一個簡易的 Web Api 專案&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#前言基本觀念&#34;&gt;前言、基本觀念&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是-api&#34;&gt;什麼是 API&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是-restful-api&#34;&gt;什麼是 Restful API&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是-http-request-和-response&#34;&gt;什麼是 HTTP Request 和 Response&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是-http-status-code&#34;&gt;什麼是 HTTP Status Code&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是-stateless&#34;&gt;什麼是 Stateless&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#正式開工&#34;&gt;正式開工&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#新建-net-core-web-api-專案&#34;&gt;新建 .net Core Web API 專案&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#動手實作-crud&#34;&gt;動手實作 CRUD&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#本系列文章&#34;&gt;本系列文章&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;前言基本觀念&#34;&gt;前言、基本觀念&lt;/h2&gt;
&lt;p&gt;我們在 &lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;上一篇&lt;/a&gt; 記錄了新訓第一天的 Git 操作筆記。接著在這篇，我們終於要進入 .net Core 啦！&lt;/p&gt;
&lt;p&gt;目前的規劃是先從建立一個可以使用的、最簡單版本的 Web Api 服務開始，再將各個工具擴增進來。所以後續的文章應該都會以這篇的簡易 API 為基底繼續延伸下去（如果順利的話啦）&lt;/p&gt;
&lt;p&gt;這篇文章的前半段會用來記錄一些&lt;strong&gt;使用或開發 API 常用到的相關知識&lt;/strong&gt;，如果對 HTTP 的部分已經有點頭緒，或是迫不及待想直接動手用 .net Core 開 Api 服務的朋友們，可以直接跳到 &lt;a href=&#34;#%E6%AD%A3%E5%BC%8F%E9%96%8B%E5%B7%A5&#34;&gt;正式開工&lt;/a&gt; 的部份。那麼，我們開始吧～&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是-api&#34;&gt;什麼是 API&lt;/h3&gt;
&lt;p&gt;我們在物件導向的 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface/&#34;&gt;介面&lt;/a&gt; 時有稍微聊過所謂介面（Interface）的概念：「在兩個系統，或是兩個分層之間要介接的時候，只需要提供我這個功能的接口／介面給對方，就能讓對方知道如何使用」&lt;/p&gt;
&lt;p&gt;API（Application Programming Interface）也是同樣的道理：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在不同的應用程式或服務（Application）之間，使用程式碼（Programming）的方式提供一組 介面（Interface），讓提供方和使用方可以藉由這組介面銜接起來。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;API 最貼切的比喻就是我們在 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;封裝篇&lt;/a&gt; 也用過的&lt;strong&gt;販賣機&lt;/strong&gt;：販賣機會提供不同飲料的按鈕，當我們選擇了其中一個按鈕按下、投了錢之後，對應的飲料就會掉下來。&lt;/p&gt;
&lt;p&gt;對應回來就是：我們到了某個服務（販賣機），去拿我們想要的資料（飲料），所以呼叫了該服務的某支 API（按鈕）並且提供了一些該 API 要求的資料（投錢），最後 API 就會把我們想要的資料交給我們（飲料）&lt;/p&gt;
&lt;p&gt;再用更實際的例子來說就像是：假設我們想要做一款可以查詢台北市的公車動態的 APP，於是我們到了提供公車動態的運輸資料服務 &lt;a href=&#34;https://tdx.transportdata.tw/api-service/swagger/basic/2998e851-81d0-40f5-b26d-77e2f5ac4118#/&#34;&gt;TDX (Transport Data eXchange)&lt;/a&gt; 去找我們想要的 API，過程中我們可能需要告訴服務我們要查的是台北市，最後服務就會將公車動態的資料交給我們。&lt;/p&gt;
&lt;p&gt;關於 API 的部份，推薦可以先閱讀過 Huli 大大的這兩篇，將基本觀念說明的相當好懂且透徹：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@hulitw/learning-tcp-ip-http-via-sending-letter-5d3299203660&#34;&gt;從傳紙條輕鬆學習基本網路概念&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@hulitw/ramen-and-api-6238437dc544&#34;&gt;從拉麵店的販賣機理解什麼是 API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，也推一下我在 CodingBar 看到的這篇 &lt;a href=&#34;https://medium.com/codingbar/api-%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E9%BA%BC-%E7%94%A8%E7%99%BD%E8%A9%B1%E6%96%87%E5%B8%B6%E4%BD%A0%E8%AA%8D%E8%AD%98-95f65a9cfc33&#34;&gt;API 到底是什麼？ 用白話文帶你認識&lt;/a&gt; 和它所引用的影片：&lt;/p&gt;
&lt;iframe src=&#34;https://www.youtube.com/embed/zvKadd9Cflc&#34; width=&#34;100%&#34; height=&#34;480&#34; frameborder=&#34;0&#34; scrolling=&#34;no&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是-restful-api&#34;&gt;什麼是 Restful API&lt;/h3&gt;
&lt;p&gt;都提到 API 了，當然不能不提 Restful 了。&lt;strong&gt;Restful 說起來比較像是 API 界的一種流派&lt;/strong&gt;，大概就像「飛天御劍流」之於「劍道」的關係一樣。只不過因為 Restful 太 Restful（？），所以幾乎已經成為主流了。&lt;/p&gt;
&lt;p&gt;那麼 Restful 到底是什麼呢？&lt;/p&gt;
&lt;p&gt;前一段我們有提過，API 的一個重點是：提供我這個功能需要的接口／介面給對方，就能讓對方知道如何使用。&lt;/p&gt;
&lt;p&gt;如果有點進去看 Huli 大大的文章，尤其是 &lt;a href=&#34;https://medium.com/@hulitw/learning-tcp-ip-http-via-sending-letter-5d3299203660&#34;&gt;傳紙條&lt;/a&gt; 那一篇的話，應該會察覺到在資料交換的過程中，最重要的一環就是建立共識，或者說是建立原則以降低溝通成本。&lt;/p&gt;
&lt;p&gt;Restful 想做的也是同一件事：只要大家都有一個制定路由的共識，就可以降低閱讀和維護成本。&lt;/p&gt;
&lt;p&gt;原先的 API 在路由的制定上並沒有什麼原則，幾乎就是看提供者爽怎麼訂就怎麼訂：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/api/create-order
/api/getProduct?id=1
/api/productDetailByProductId
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但這樣就會造成額外的溝通成本。&lt;/p&gt;
&lt;p&gt;為了消除這些溝通成本，Restful 就出現了！Restful 提倡使用「&lt;strong&gt;符合 HTTP 語意&lt;/strong&gt;」和「&lt;strong&gt;以資源為主&lt;/strong&gt;」的方式來處理 API 的路由。&lt;/p&gt;
&lt;p&gt;我們用白話一點的方式來理解：大多數時候，我們發出的請求都是「對『什麼東西』做『什麼動作』」，也就是通常會有一個「&lt;strong&gt;動詞&lt;/strong&gt;」和一個「&lt;strong&gt;目標&lt;/strong&gt;」，Restful 就是從這兩個部份下手來達成共識。&lt;/p&gt;
&lt;p&gt;那我們從「動作」的部分開始，也就是建立「符合 HTTP 語意」的共識：&lt;strong&gt;我們可以利用 HTTP 定義的請求方法（HTTP Method，也被稱為 HTTP 動作）來表達我們的要求&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;上面推薦過的 Huli 大大這篇&lt;a href=&#34;https://medium.com/@hulitw/learning-tcp-ip-http-via-sending-letter-5d3299203660&#34;&gt;傳紙條文章&lt;/a&gt;中有提到，因為能做的事情太多了，光是一個訂便當相關的動作，既可以訂購便當，也要能查有哪些便當可以訂，或是訂好了卻要換成別的便當等等。因此為了將動作做個統一，就在紙條的 Header 加了欄位，用來&lt;strong&gt;標示這次動作是哪種類型&lt;/strong&gt;，這就是所謂的 HTTP 方法。&lt;/p&gt;
&lt;h4 id=&#34;常見的-http-method&#34;&gt;常見的 HTTP Method&lt;/h4&gt;
&lt;p&gt;我們比較常見的 HTTP 方法有這幾個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;用來&lt;strong&gt;查詢&lt;/strong&gt;、取得資源。例如說「&lt;strong&gt;Get 最新的一筆訂單&lt;/strong&gt;」&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;用來&lt;strong&gt;建立&lt;/strong&gt;新資源。例如說「&lt;strong&gt;Post 一筆新訂單&lt;/strong&gt;」&lt;/li&gt;
&lt;li&gt;也可以用來當萬用動詞，就是不太確定歸類在哪一個、使用其他動詞遇到障礙的時候使用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PUT&lt;/code&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;通常用來&lt;strong&gt;更新&lt;/strong&gt;現有的資源。例如說「&lt;strong&gt;Put 一號訂單的新訂單內容&lt;/strong&gt;」&lt;/li&gt;
&lt;li&gt;如果服務有額外處理的話，有時候也會做成 沒有這筆資源就新增一筆&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PATCH&lt;/code&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;更新資源的時候做「&lt;strong&gt;部分更新&lt;/strong&gt;」。例如說「&lt;strong&gt;Patch 一號訂單的出貨狀況&lt;/strong&gt;」&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;用來&lt;strong&gt;刪除&lt;/strong&gt;資源，例如說「&lt;strong&gt;Delete 一號訂單&lt;/strong&gt;」&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中有幾個部分可能會讓人有點疑惑，這邊稍微整理一下，也歡迎大家補充：&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;put-vs-patch-都是更新&#34;&gt;PUT vs PATCH 都是更新？&lt;/h4&gt;
&lt;p&gt;兩者的更新方式不太一樣。通常 PUT 會指整批更新、PATCH 是指部分更新。&lt;/p&gt;
&lt;p&gt;以產品舉例的話：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PUT&lt;/code&gt; 比較像是把整個產品資料丟上去，然後整團更新掉&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PATCH&lt;/code&gt; 則是只更新一部份，像是只丟產品名稱上去，然後就只更新名稱&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&#34;post-的萬用動詞是什麼意思&#34;&gt;POST 的「萬用動詞」是什麼意思？&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;我們有時還是會遇到不太確定要歸類到哪個動詞，或是其他動詞處理不來的情況，預設就使用 &lt;code&gt;POST&lt;/code&gt; 處理&lt;/li&gt;
&lt;li&gt;例如說，以 &lt;code&gt;GET&lt;/code&gt; 查詢的時候，參數的規劃有些問題導致於 QueryString 放不太下，就可以考慮改用 &lt;code&gt;POST&lt;/code&gt;，並將參數放入 Body&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&#34;安全safe冪等idempotent&#34;&gt;安全（Safe）、冪等（idempotent）&lt;/h4&gt;
&lt;p&gt;在查詢 HTTP 動作的時候（例如維基百科的&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE#%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95&#34;&gt;請求方法&lt;/a&gt;），總是會看到「安全（Safe）」、「冪等（idempotent）」這些用詞，是什麼意思？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;安全（Safe）的方法是指沒有對資料進行變更的操作&lt;/strong&gt;。例如：
&lt;ul&gt;
&lt;li&gt;當我 &lt;code&gt;GET&lt;/code&gt; 的時候，我單純只是查詢資料&lt;br/&gt;不會對資料進行任何變動，所以是安全的&lt;/li&gt;
&lt;li&gt;反過來說像是 &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; 這些會對資料進行變更的方法就是不安全的&lt;/li&gt;
&lt;li&gt;使用不安全的方法的時候，就得要考慮用特殊的方式告訴系統的使用者該操作可能的變更。例如前端呼叫前會跳出視窗警告：「確認是否刪除」等等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;冪等（idempotent）則是指該方法在同樣的條件下，不管重複作幾次，對資源的結果都相同&lt;/strong&gt;。例如：
&lt;ul&gt;
&lt;li&gt;我一直 &lt;code&gt;GET&lt;/code&gt;，不管我 &lt;code&gt;GET&lt;/code&gt; 幾次，資料還是同樣那一份不會變動&lt;/li&gt;
&lt;li&gt;我一直 &lt;code&gt;PUT&lt;/code&gt;，不管我 &lt;code&gt;PUT&lt;/code&gt; 同樣的參數幾次，資料的結果還是會長一樣&lt;/li&gt;
&lt;li&gt;我一直 &lt;code&gt;DELETE&lt;/code&gt;，不管我 &lt;code&gt;DELETE&lt;/code&gt; 幾次，那一份資料都一樣不在了&lt;/li&gt;
&lt;li&gt;我一直 &lt;code&gt;POST&lt;/code&gt; ，就會一直長出新資料（！注意，不是 idempotent ！）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PATCH&lt;/code&gt; 的部份比較特殊一點：
&lt;ul&gt;
&lt;li&gt;因為我們要求更新這個資源的某一些部份的時候，並不能確定其他地方會不會變動&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;例如說，我們更新了 產品描述 這個欄位，結果其實還有一個欄位是 版本號，然後服務偵測到每次更新都會自動增加。&lt;br/&gt;這樣我們就算重複 &lt;code&gt;PATCH&lt;/code&gt; 結果也有可能不一樣，因此 &lt;code&gt;PATCH&lt;/code&gt; 並不能算是 idempotent 的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;當我們知道一支 API 是不是安全的、是不是冪等的，也就可以幫助我們知道這支 API 能不能重試，當遇到某些問題時能不能大膽地再打一次請求過去。&lt;/p&gt;
&lt;p&gt;例如說連續查詢兩次並不會對資料進行任何變動，就可以原地重查一次；但面對扣款這種危險狀況，我們當然就不會想再扣一次錢。當我們確實按照安全和冪等這些特性來運用 HTTP 動詞，就可以讓使用端得到這些資訊。&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;小小結與推薦閱讀&#34;&gt;小小結與推薦閱讀&lt;/h4&gt;
&lt;p&gt;那麼補充就先到這裡。除了上面只列出了我個人平常比較常接觸的 HTTP 方法之外，其他還有 &lt;code&gt;HEAD&lt;/code&gt;、&lt;code&gt;TRACE&lt;/code&gt; 等等，有興趣的朋友可以再逛逛 MDN 的 &lt;a href=&#34;https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods&#34;&gt;HTTP 請求方法&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;現在我們大概知道了有這些 HTTP 方法可以使用，也因此已經將動作抽離出來了。接著就輪到我們要進行這些動作的「目標」了，這部份則要求：我們在制定路由的時候，應該從「&lt;strong&gt;資源&lt;/strong&gt;」的角度下去考慮。&lt;/p&gt;
&lt;p&gt;這相當直覺，例如「新增 訂單」、「查詢 產品」這樣，我們動作後面接續的應該要是一個資源。從這個方式下去定的話，上面的範例應該就會變成：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;POST /api/order
GET /api/product/1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;很明確就能夠看出&lt;strong&gt;意圖&lt;/strong&gt;。這邊就讓我們來看看微軟的 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/architecture/best-practices/api-design#define-operations-in-terms-of-http-methods&#34;&gt;Web Api 設計&lt;/a&gt; 文中的範例表格：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Resource&lt;/th&gt;
          &lt;th&gt;POST&lt;/th&gt;
          &lt;th&gt;GET&lt;/th&gt;
          &lt;th&gt;PUT&lt;/th&gt;
          &lt;th&gt;DELETE&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;/customers&lt;/td&gt;
          &lt;td&gt;建立新客戶&lt;/td&gt;
          &lt;td&gt;擷取所有客戶&lt;/td&gt;
          &lt;td&gt;大量更新客戶&lt;/td&gt;
          &lt;td&gt;移除所有客戶&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;/customers/1&lt;/td&gt;
          &lt;td&gt;錯誤&lt;/td&gt;
          &lt;td&gt;擷取客戶 1 的詳細資料&lt;/td&gt;
          &lt;td&gt;更新客戶 1 的詳細資料 (若有的話)&lt;/td&gt;
          &lt;td&gt;移除客戶 1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;/customers/1/orders&lt;/td&gt;
          &lt;td&gt;為客戶 1 建立新訂單&lt;/td&gt;
          &lt;td&gt;擷取客戶 1 的所有訂單&lt;/td&gt;
          &lt;td&gt;大量更新客戶 1 的訂單&lt;/td&gt;
          &lt;td&gt;移除客戶 1 的所有訂單&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;br/&gt;
&lt;p&gt;這邊大致上有個感覺就行了，畢竟 Restful 是一種風格，並沒有強烈的規定，只要符合共識和約束，就可以算是 Restful 了。&lt;/p&gt;
&lt;p&gt;在微軟把拔的 &lt;a href=&#34;https://learn.microsoft.com/zh-tw/azure/architecture/best-practices/api-design&#34;&gt;RESTful Web API 設計&lt;/a&gt; 中，援引了 Leonard Richardson 的 API 成熟度模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;等級 0：定義一個 URI，而所有的作業對此 URI 都是 POST 要求&lt;/li&gt;
&lt;li&gt;等級 1：針對個別資源建立不同的 URI&lt;/li&gt;
&lt;li&gt;等級 2：使用 HTTP 方法來定義資源上的作業&lt;/li&gt;
&lt;li&gt;等級 3：使用&lt;a href=&#34;https://learn.microsoft.com/zh-tw/azure/architecture/best-practices/api-design#use-hateoas-to-enable-navigation-to-related-resources&#34;&gt;超媒體 (HATEOAS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目前就內文所述，大多數的已發行 API 約略是等級 2 附近，也就是這次介紹的：使用 HTTP 動詞 + 從資源出發的設計方針。只要掌握這個方向，基本上就沒什麼問題了。&lt;/p&gt;
&lt;p&gt;同樣的，這個訂立的原則，也就得仰賴多去看人家的 Api 怎麼設計來培養了。但只要把握幾個重點：建立共識、提高可讀性，還有設計的時候好好和來介接的夥伴喬一下，基本上就不會有什麼差錯啦，畢竟這東西很彈性的。&lt;/p&gt;
&lt;p&gt;當然，本篇只是稍微說明而已，非常鼓勵各位朋友再進一步搜尋了解。最後推一下這兩篇 Restful API 相關的文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://progressbar.tw/posts/53&#34;&gt;休息(REST)式架構? 寧靜式(RESTful)的Web API是現在的潮流？&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;我個人覺得寫得很清楚明瞭，並且範例也相當好懂，咱們當初新訓的時候也是看這篇來了解概念的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tw.twincl.com/programming/*641y&#34;&gt;簡明 RESTful API 設計要點&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;針對一些要點有說明，有興趣的朋友可以稍微看過一遍。內文提到的 HTTP Status 我們等等也會進行說明&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是-http-request-和-response&#34;&gt;什麼是 HTTP Request 和 Response&lt;/h3&gt;
&lt;p&gt;現在讓我們運用動詞（HTTP 動詞）和名詞（Restful 風格的路由），就可以組出像是 &lt;code&gt;GET product/1&lt;/code&gt; 這樣子的 Api 路徑。用寄送信封的方式來說的話，現在我們就等於已經掌握了寄送的地址。&lt;/p&gt;
&lt;p&gt;我們有了地址之後，才能寄信到這個地址給對方，並且等待對方的回信（話說這年頭大家都用網路通訊了，會不會過幾年沒人看得懂這組經典比喻啊？）。&lt;/p&gt;
&lt;p&gt;而我們有了 Api 的地址（URL）之後，就可以&lt;strong&gt;對這個地址上的服務送出一個 Request（請求），服務接受到請求之後，就會給予我們一個 Response（回應）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這個送出請求、取得回應的動作，就是網路運作的原理。例如當你打開瀏覽器到某個網站，就是對該網站發出一個取得網站內容的請求，網站再給你網站內容的回應。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充一下，聰明的朋友可能發現了，我們前面介紹的 Api 也就是這組動作：當我們呼叫某個服務，例如 Google Map 的 Api，也就是對目標的 Api 發出了一個想要使用服務的請求，服務再針對請求給予回應。因此，要想了解 Api 的各個知識點，接觸 HTTP 是必不可免的。&lt;/p&gt;
&lt;p&gt;但 HTTP 這水挺深的哪，所以我們這篇的紀錄先以「打 Api 服務的時候不至於都看不懂」為目標進行，稍微介紹一下 Request 和 Response 的內容吧。來日方長嘛。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Request 和 Response 的內容，有興趣的朋友可以直接在瀏覽器進入開發人員工具就能看到了，以 Edge 和 Chrome 為例：首先按下 F12 ，並找到網路（Network）的部分，就可以看到進入網頁的各個請求和回應，隨便點開一個就會有 Request 和 Response 的內容囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/kdK0xo1.webp&#34;
  alt=&#34;&#34;width=&#34;1920&#34; height=&#34;899&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;或者是可以直接參考 NotFalse 技術客的這篇 &lt;a href=&#34;https://notfalse.net/39/http-message-format&#34;&gt;HTTP/1.1 — 訊息格式 (Message Format)&lt;/a&gt;，裡面對 HTTP 請求格式的各個部分上色並逐步講解的方式讓我蠻喜歡的，相當清楚。&lt;/p&gt;
&lt;p&gt;接著就讓我們來看一下訊息的格式，通常會分成三個比較主要的區塊：&lt;strong&gt;Start Line&lt;/strong&gt;、&lt;strong&gt;Header&lt;/strong&gt;、&lt;strong&gt;Body&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-JSON&#34; data-lang=&#34;JSON&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;HTTP/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;OK&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// Start Line
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Header
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Content-Type:&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;application/json;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;charset=utf&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;-8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Date:&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Mon,&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Apr&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2021&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;31&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GMT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Body
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nike&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;category&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shoes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;首先讓我們看看 Start Line：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Json&#34; data-lang=&#34;Json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;HTTP/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;OK&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// Start Line
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Start Line 在 Request 的時候又叫做 Request-line，會標明 HTTP Method、URL、HTTP 版本。&lt;/p&gt;
&lt;p&gt;例如 &lt;code&gt;GET /product/1 HTTP/1.1&lt;/code&gt;。我們 &lt;a href=&#34;#%E9%97%9C%E6%96%BC-restful-%E8%88%87-http-method&#34;&gt;上一節&lt;/a&gt; 學的 HTTP 動作（GET、POST…）和路由地址（&lt;code&gt;/product/1&lt;/code&gt;）就是用在這裡。&lt;/p&gt;
&lt;p&gt;而 Start Line 在 Response 的時候又叫做 Status-line，會標明 HTTP 版本、HTTP 狀態碼（Status Code）和描述。&lt;/p&gt;
&lt;p&gt;例如 &lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;，這個狀態碼就是 Response 中的主要回應，我們在 &lt;a href=&#34;#%E9%97%9C%E6%96%BC-http-status-code&#34;&gt;下一節&lt;/a&gt; 會介紹一些常看到的 Status Code，現在先知道這裡就是服務用來告訴你「好呀」跟「不要」的地方即可。&lt;/p&gt;
&lt;p&gt;Start Line 會放在整個訊息的第一行，之後會用個空行做分隔，後面再接著 Header。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;讓我們順著來看看 Header：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Json&#34; data-lang=&#34;Json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Header
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Content-Type:&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;application/json;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;charset=utf&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;-8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Date:&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Mon,&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Apr&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2021&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;31&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GMT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Header 又叫作標頭、表頭等，主要是用來放這次訊息相關的參數和資訊&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如說用 Content-Type 來說明這次訊息傳輸的格式，可能是表示 HTML 的 &lt;code&gt;text/html&lt;/code&gt;，或者是表示 JSON 的 &lt;code&gt;application/json&lt;/code&gt; 等等；也有可能用 &lt;code&gt;cookie&lt;/code&gt; 來表示瀏覽器當前紀錄的資訊等等。&lt;/p&gt;
&lt;p&gt;Header 可以放的資訊有挺多種的，有興趣的朋友可以看看這篇 &lt;a href=&#34;https://medium.com/ttyy2985/http%E5%89%8D%E5%BE%8C%E7%AB%AF%E5%82%B3%E8%BC%B8%E6%B5%81%E7%A8%8B-8ee40ffca1bd&#34;&gt;HTTP 前後端傳輸流程&lt;/a&gt;，列出了常見的 HTTP Header 欄位；如果好奇上面提到的 Content-Type 有那些常用的種類，也可以參照這篇 &lt;a href=&#34;https://medium.com/hobo-engineer/ricky%E7%AD%86%E8%A8%98-postman-%E5%B8%B8%E8%A6%8B%E7%9A%84-content-type-b17a75396668&#34;&gt;Postman 常見的 Content-type&lt;/a&gt;，這邊就先按下不表。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：除了規範的 Header 欄位以外，我們有時也會在 Header 裡放一些自訂的欄位。以前的慣例是用 &lt;code&gt;X-&lt;/code&gt; 開頭來標示，例如 &lt;code&gt;X-Custom-Header&lt;/code&gt;，但 &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc6648&#34;&gt;RFC 6648&lt;/a&gt; 建議停止繼續用 &lt;code&gt;X-&lt;/code&gt;，而是直接用好讀好懂的名稱來定 Header 就好了。&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Header 結束之後，按照訊息的種類和內容，可以再加上 Body，兩者之間會空一行隔開：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Json&#34; data-lang=&#34;Json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Body
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nike&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;category&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shoes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Body 又叫做回應主體，是非必須、選填的，通常我們會用來存放本次訊息需要的資料&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以寄信來舉例的話，Header 就像是外面的 寄信人、收件人等資訊，而 Body 則是裡面的信件內文。但要注意，一個訊息並不一定要有 Body，就像我們也可以只寄送明信片一樣。很常聽到的一個比喻就是：GET 就像明信片、POST 就像是包裹，中間就是需不需要附加 Body 的差別。&lt;/p&gt;
&lt;p&gt;當 GET 的時候，我們通常並不會用到 Body，如果有需要附上參數的話，GET 我們會放到 QueryString（就是接續在往指後面，常常見到的 ?a=1&amp;amp;b=2 那串）。&lt;/p&gt;
&lt;p&gt;例如說取得產品列表，我們需要向 &lt;code&gt;/product&lt;/code&gt; 發出 &lt;code&gt;GET&lt;/code&gt; 請求，同時我們又只想要類別是鞋子、關鍵字為 Nike 的產品，那我們組出來的整個 URL 和 QueryString 可能就是這樣的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-JSON&#34; data-lang=&#34;JSON&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GET&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/product?category=shoes&amp;amp;keyword=nike&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/*&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;這邊會有一些&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Header&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其他的場景，例如 POST 時，如果有需要附上資料，我們通常會用到 Json 或 XML 之類的格式放到 Body 中。例如我們現在要新增一筆新的產品，是叫做 Nike 的鞋子，那可能就會附上：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-JSON&#34; data-lang=&#34;JSON&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;POST&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/product&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/*&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;這邊會有一些&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Header&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nike&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;category&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shoes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果是 XML 的話，可能就會是這樣表示：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-XML&#34; data-lang=&#34;XML&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;root&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;nike&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;category&amp;gt;&lt;/span&gt;shoes&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/category&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/root&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當然也有可能是用其他的格式，只要服務願意接受，兩邊能夠通訊其實也就沒什麼關係，說到底網頁最基本的部分也還是一個回傳 HTML 格式的 Response。所以各位如果遇見了沒看過的格式也不用驚慌，因為放在這裡都一樣是用來表示資料的格式，只是表達的方式不一樣而已。&lt;/p&gt;
&lt;p&gt;不過我平常開發和使用 API 都是用 JSON 的場合居多（個人覺得體感上 JSON 已經快一統 API 的天下了），強烈建議至少還是要能看懂 JSON。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：關於 JSON 如果不太了解的朋友，可以參閱 &lt;a href=&#34;http://miniaspreading.github.io/guide-to-json/1-what-is-json.html&#34;&gt;JSON精要讀書紀錄&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;最後再複習一次，當我們呼叫一個 API 的時候，我們可能會送出一個 HTTP Request（請求）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-JSON&#34; data-lang=&#34;JSON&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GET&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/product/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;HTTP/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// Start Line
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Header
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;user-agent:&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Mozilla/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5.0&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;(Windows&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;NT&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10.0&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Win&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;//...其他一堆標頭
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後服務就會回給我們一個 HTTP Response（回應）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-JSON&#34; data-lang=&#34;JSON&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;HTTP/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;OK&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// Start Line
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Header
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Content-Type:&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;application/json;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;charset=utf&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;-8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Date:&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Mon,&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;Apr&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2021&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;31&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GMT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Body
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nike&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;category&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shoes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣我們就成功交換資料啦！&lt;/p&gt;
&lt;p&gt;……當然，所謂天有不測風雲，API 有 500 Internal Server ERROR，沒有每次打 API 都一定是 OK 的啦，其他什麼 404, 500 動不動就會跑出來。&lt;/p&gt;
&lt;p&gt;就像我們去網路購物的時候，按下購買把購物車清空後，網頁就會告訴我們「訂單成立」或是「結帳失敗」等等，當 API 的使用者將這個要求發送給 API 服務，服務就會告訴使用者針對這個要求的回應。這就是我們之前提到的 HTTP 狀態碼（Status Code）出場的時候了。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是-http-status-code&#34;&gt;什麼是 HTTP Status Code&lt;/h3&gt;
&lt;p&gt;在上個小節中我們有提到，&lt;strong&gt;Response 的 Start Line 會帶著一組 HTTP 狀態碼（Status Code）和描述，用來告訴你這次請求的回應&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;大概就像是&lt;strong&gt;告白&lt;/strong&gt;之後的好呀、謝謝你、你是個好人等等，回應也有分成很多種，這邊就稍微紀錄一下平常遇到的幾種狀態。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;1xx: 參考資訊&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大多是在正式回應之前先暫時給使用者一些資訊。就像是「先讓我考慮一下…」&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;2xx: 成功&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;200: 要求成功&lt;/strong&gt;。就像是「好呀」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;201: 已建立&lt;/strong&gt;。要求新增資源的時候會用到，就像是「OK，那我掛穩交囉」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;204: 無內容&lt;/strong&gt;。雖然點頭了，可是什麼話也沒說&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;3xx: 重新導向&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;301: 永久轉址&lt;/strong&gt;。就像是傳了簡訊之後得到「她換手機了，我是她爸」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;303: 臨時轉址&lt;/strong&gt;，原本存在但是換了位置，就像是打電話過去結果「她出門了，我是她爸」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;304: 沒有變動&lt;/strong&gt;，可以直接去快取拿就好。就像是「說啥呢，我們都結婚五十年了= =」&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;4xx: 用戶端錯誤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;400: 要求錯誤&lt;/strong&gt;，大多數使用者造成無法處理的問題都會丟到這，可能是&lt;br/&gt;參數沒給、這個網域不存在等等。就像是『她』說「其實我是 ♂ 的呦，沒關係嗎？」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;401: 拒絕存取&lt;/strong&gt;，可能是沒有登入、授權失敗等等。&lt;br/&gt;就像是「你是誰？！我根本不認識你啊？！」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;403: 禁止使用&lt;/strong&gt;，可能是沒有權限、沒有憑證、被拒絕等等。&lt;br/&gt;就像是「抱歉，你的長相實在…抱歉。」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;404: 找不到&lt;/strong&gt;，這個應該是最常見的用戶端錯誤了。&lt;br/&gt;就像是「你認錯人了吧…？」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;418: &lt;a href=&#34;https://blog.huli.tw/2019/06/14/http-status-code-418-teapot/&#34;&gt;我是個茶壺&lt;/a&gt;&lt;/strong&gt;，啊人家就只是個茶壺…&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;5xx: 伺服器錯誤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;500: 內部伺服器錯誤&lt;/strong&gt;，大多數問題都會被丟到這一類，我們開發人員頭痛的時候就到了…&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;501: 未實作&lt;/strong&gt;，通常對一個只提供 &lt;code&gt;GET&lt;/code&gt; 的 &lt;code&gt;URL&lt;/code&gt; 打 &lt;code&gt;POST&lt;/code&gt; 就會看到了。&lt;br/&gt;就像是「我已經結婚囉，謝謝你呢 ^^」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;502: 無效回應&lt;/strong&gt;。就像是「我也喜歡哆啦Ａ夢呢」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;503: 伺服器維護或過載&lt;/strong&gt;。追求者太多了，根本沒空回你。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;504: 閘道逾時&lt;/strong&gt;。（已讀不回）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;對還有哪些 HTTP Status 或是想對每個 HTTP Status 發生的狀況了解得更詳細的朋友，可以參閱以下兩篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://noob.tw/http-status-code/&#34;&gt;常見與不常見的 HTTP Status Code - Noob&amp;rsquo;s Space&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;除了列出常見的 HTTP 狀態，還收錄了一些非官方和惡搞的&lt;/li&gt;
&lt;li&gt;看了這篇知道更多 HTTP Status Code 之後，本網站正考慮支持 HTTP Status 735&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2009/01/16/Web-developer-should-know-about-HTTP-Status-Code&#34;&gt;網頁開發人員應了解的 HTTP 狀態碼 - The Will Will Web&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;針對每個狀態有更詳細的說明，並列出了更細的 IIS 擴充狀態，例如 401.1 - 登入失敗等等&lt;/li&gt;
&lt;li&gt;強力推薦 Dotnet 相關的開發人員看個一遍有個印象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是-stateless&#34;&gt;什麼是 Stateless&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;2021.05.04: 發文之後發現忘記提到無狀態了，所以這邊回來補充一下。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接續前面提到的 Restful 風格，在 API 的制定上，Restful 還有另一個要求，就是必須是 &lt;strong&gt;無狀態的（Stateless）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;無狀態是指：伺服器不會儲存使用者的狀態，當使用者呼叫 API 的時候，應該給予所有 API 需要的內容。&lt;/p&gt;
&lt;p&gt;用比較的方式來說：原本的 API 設計可能會保存使用者的動作狀態，然後留到下一次呼叫的時候再接著使用，那這樣的 API 設計的時候就像是制定食譜，必須要求使用者照順序才可以正常運作。&lt;/p&gt;
&lt;p&gt;例如必須先打Ａ，再打Ｂ，最後打Ｃ，沒有照順序打的話就會因為狀態不對而壞掉。但這樣的做法其實就是所謂的 &lt;a href=&#34;https://dotblogs.azurewebsites.net/Im_sqz777/2021/04/18/what-is-temporal-coupling&#34;&gt;時序耦合&lt;/a&gt;，不僅造成測試和使用上的困擾，也相當的不直覺、相當的有「味道」。&lt;/p&gt;
&lt;p&gt;而我們在 Restful 的時候，是用資源的方式下去考慮，所以應該把重點放在資源的操作，而不是狀態的管理和流程，否則就會很奇怪。例如說你去買雞蛋，然後老闆突然跟你說：不行喔，因為你還沒買過牛奶。哇，整個就莫名其妙。&lt;/p&gt;
&lt;p&gt;所以在 Restful 的設計上，&lt;strong&gt;每一次呼叫都應該要是獨立的呼叫&lt;/strong&gt;，設計的時候就是以資源為主體，再利用這些資源互相搭配來完成功能。&lt;/p&gt;
&lt;p&gt;例如說「登入之後跳出使用者待結帳的訂單，並且 Show 出訂單中的產品」這種情況，我們就不該要求使用者一定要照順序打，然後伺服器再來記住登入的資訊、使用者的編號、訂單的編號等等，除了記錄一堆多餘的資訊，更把 API 的使用場景給卡死了，相當不 OK。&lt;/p&gt;
&lt;p&gt;這邊用資源的角度出發的話，應該讓「登入」、「查詢訂單（使用者呼叫時提供使用者編號）」、「查詢產品（使用者呼叫時提供訂單編號）」組合起來，從「將這些資源的操作組合在一起完成功能」的角度出發，那在別的流程就可以重複使用這些 API 來組成功能，同時也能分別對各個功能進行開發跟測試。&lt;/p&gt;
&lt;p&gt;如此一來，就能提升彈性、自由度。同時，因為伺服器不用紀錄狀態，讓每一次呼叫都是獨立的呼叫，不只可以重複使用，聚焦在資源上也能讓操作變得簡潔。進一步來說，因為狀態不會被綁死在特定的機器上，那就可以開始搞擴大加機器做分散式啦。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充一下：當然，要做到完全沒有狀態是很難的，但我們可以做到讓伺服器不要綁死、每個呼叫都是盡量獨立的，來盡可能使得服務乾淨、有彈性、可分散處理。&lt;/p&gt;
&lt;p&gt;同時也因為這樣，為了讓使用者的狀態不會遺失（例如登入狀態），就得做到使用者的每個呼叫都要能夠證明自己的身份，所以才有了壓成 Token、把身分驗證的伺服器分離等等的驗證做法，這個部份我們有空再來填坑吧。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;正式開工&#34;&gt;正式開工&lt;/h2&gt;
&lt;p&gt;現在，我們已經大概了解 API 以及 Restful API 的一些相關知識了，接著就讓我們來實際建立一個簡單的 API 服務吧！&lt;/p&gt;
&lt;h3 id=&#34;新建-net-core-web-api-專案&#34;&gt;新建 .net Core Web API 專案&lt;/h3&gt;
&lt;p&gt;首先打開我們的 Visual Studio，建立新的專案。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tRvVsFa.webp&#34;
  alt=&#34;&#34;width=&#34;352&#34; height=&#34;85&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們這次用 .net Core 來進行示範，並且用官方內建的 Web API 框架直接開場，所以這邊選擇 Asp.net Core Web API。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lyjhWFS.webp&#34;
  alt=&#34;&#34;width=&#34;975&#34; height=&#34;654&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：這些&lt;strong&gt;建立專案的畫面和選項隨著版本可能會有點不一樣&lt;/strong&gt;，例如說可能會先選擇 .net Core 的 Web 服務之後，後續再勾選 API 的選項等等。例如之前的 &lt;a href=&#34;https://igouist.github.io/post/2019/12/aspnet-connect-db&#34;&gt;Asp.net MVC&lt;/a&gt; 文章中，就需要在選擇範本的時候選擇是 MVC 或 Web API 的範本。就麻煩各位再稍微見機行事一下。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/NCQj9Wf.webp&#34;
  alt=&#34;&#34;width=&#34;959&#34; height=&#34;636&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著輸入專案名稱和選擇專案路徑，我在這邊採用 Newbie/Noob 的 N（而且 Project N 感覺很潮？），各位嘗試的時候可以自由取名，但要注意後續用到 &lt;code&gt;ProjectN&lt;/code&gt; 這個名字的部分必須和你取的名字一致呦。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Lbr3qkw.webp&#34;
  alt=&#34;&#34;width=&#34;954&#34; height=&#34;637&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是紀錄採用的版本為長期支援的 .net Core 3.1（如果這個步驟有選擇其他版本的朋友，後續安裝套件的時候可能要注意一下版本相容性的問題）&lt;/p&gt;
&lt;p&gt;建立之後我們的專案結構應該會長這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lGGHzDN.webp&#34;
  alt=&#34;&#34;width=&#34;295&#34; height=&#34;277&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;恭喜各位，到這一步的時候，&lt;strong&gt;Web API 服務已經建好了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;直接用範本就是這麼爽，謝謝微軟把拔。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;我們先來稍微認識一下環境設定相關的成員：&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;code&gt;Properties/launchSettings.json&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用來放我們偵錯專案時套用的環境設定，例如說我們偵錯的時候預設要打開哪個頁面（launchUrl）就是在這邊設定&lt;/li&gt;
&lt;li&gt;可以參見 &lt;a href=&#34;https://blog.poychang.net/visual-studio-launch-settings-iis-express-iis-project-executable/&#34;&gt;launchSettings.json 的 commandName 是做什麼用的？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;code&gt;appsettings.json&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用來放組態資料，像是連線字串、Log 的紀錄層級等等就會丟在這&lt;/li&gt;
&lt;li&gt;以前用過 &lt;code&gt;web.config&lt;/code&gt; 的朋友可能會比較熟。但在 .net Core 已經將不同職責的設定區塊拆分出去給&lt;br/&gt; &lt;code&gt;appsettings.json&lt;/code&gt;、&lt;code&gt;.csproj&lt;/code&gt; 等等，並且可以繫結強型別，所以更乾淨了。（感謝 Mike 和 &lt;a href=&#34;https://sunnyday0932.github.io&#34;&gt;Sian&lt;/a&gt; 的說明）&lt;/li&gt;
&lt;li&gt;關於兩者的差異和讀取 &lt;code&gt;appsettings.json&lt;/code&gt; 的方法，可以參照余小章大大的這篇 &lt;a href=&#34;https://dotblogs.com.tw/yc421206/2020/06/28/how_to_read_config_appsettings_json_via_net_core_31&#34;&gt;如何讀取 AppSettings.json 組態設定檔&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;code&gt;Startup.cs&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;設定服務的行為、註冊依賴注入相關的東西就丟在這，像之前註冊 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper&#34;&gt;AutoMapper&lt;/a&gt; 服務的時候就是在這裡設定&lt;/li&gt;
&lt;li&gt;我們後續還會常常過來找 Startup 玩&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;code&gt;Program.cs&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;整個應用程式的進入點&lt;/li&gt;
&lt;li&gt;關於服務啟動之後的順序和 &lt;code&gt;Program.cs&lt;/code&gt;, &lt;code&gt;Startup.cs&lt;/code&gt; 的內容，&lt;br/&gt;可以參照 &lt;a href=&#34;https://blog.johnwu.cc/article/ironman-day02-asp-net-core-application-lifetime.html&#34;&gt;ASP.NET Core 2 系列 - 程式生命週期 (Application Lifetime) - John Wu&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;現在這個時間點，我們還不會對設定的部分動什麼手腳，只要對這些東西有個瞭解就好了。&lt;/p&gt;
&lt;p&gt;我們把鏡頭轉到剛剛沒提到的其他檔案，可以看到還有 &lt;code&gt;WeatherForecast.cs&lt;/code&gt; 和 &lt;code&gt;Controllers&lt;/code&gt; 及裡面的 &lt;code&gt;WeatherForecastController.cs&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充一下，如果之前已經對 MVC 架構有點熟悉度的朋友，應該對 &lt;code&gt;Controller&lt;/code&gt; 這個詞不陌生了。&lt;/p&gt;
&lt;p&gt;Web API 的範本其實也是同樣的概念，只是 &lt;code&gt;View&lt;/code&gt; 的部分已經交給呼叫 Api 服務的使用者去處理了，而這邊的 &lt;code&gt;WeatherForecast.cs&lt;/code&gt; 打開可以發現只是純粹的天氣資料，也就是個 &lt;code&gt;Model&lt;/code&gt; 或 &lt;code&gt;ViewModel&lt;/code&gt; 之類的東西。&lt;/p&gt;
&lt;p&gt;至於 &lt;code&gt;Controller&lt;/code&gt; 的職責仍然沒有什麼變化，就是個第一線的交通警察。所以先前做過 MVC 的朋友大概會覺得比較親近一些吧。大概。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這邊可以看到 &lt;code&gt;WeatherForecast.cs&lt;/code&gt; 只是個用在 &lt;code&gt;WeatherForecastController&lt;/code&gt; 的天氣資料類別。所以我們直接打開 &lt;code&gt;WeatherForecastController&lt;/code&gt; 來觀察一下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WeatherForecastController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;[] Summaries = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Freezing&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bracing&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Chilly&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Cool&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Mild&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Warm&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Balmy&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hot&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Sweltering&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Scorching&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ILogger&amp;lt;WeatherForecastController&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; WeatherForecastController(ILogger&amp;lt;WeatherForecastController&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;WeatherForecast&amp;gt; Get()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rng = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Random();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Enumerable.Range(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;).Select(index =&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; WeatherForecast
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Date = DateTime.Now.AddDays(index),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            TemperatureC = rng.Next(-&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;55&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Summary = Summaries[rng.Next(Summaries.Length)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .ToArray();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;首先在整個類別開始的地方加上了兩個屬性（Attribute）：&lt;code&gt;[ApiController]&lt;/code&gt; 很明顯告訴我們這是個 Api Controller，而接著的 &lt;code&gt;[Route(&amp;quot;[controller]&amp;quot;)]&lt;/code&gt; 用來決定我們這個 controller 的路由。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這邊要特別注意 &lt;code&gt;Route&lt;/code&gt; 這個屬性，我們會在 Controller 和 Function 上用這個屬性來制定我們的 Api 的 URL，也就是我們前面 &lt;a href=&#34;#%E9%97%9C%E6%96%BC-restful-%E8%88%87-http-method&#34;&gt;Restful 小節&lt;/a&gt; 提過的「用資源制定路由」要處理的部分。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;現在可以看到 &lt;code&gt;Route&lt;/code&gt; 的內容是 &lt;code&gt;[controller]&lt;/code&gt;，所以會直接採用 controller 的名稱，在這邊也就會是 &lt;code&gt;/WeatherForecast/&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果有需要額外客製化路由的話，只要修改 &lt;code&gt;Route&lt;/code&gt; 標籤就可以了，例如：改成 &lt;code&gt;Route(&amp;quot;hello&amp;quot;)&lt;/code&gt; 的話，就會變成 &lt;code&gt;/hello/&lt;/code&gt;；那如果我們接著在 Function 上掛上 &lt;code&gt;Route(&amp;quot;world&amp;quot;)&lt;/code&gt; 的話，該方法的路由就會變成 &lt;code&gt;/hello/world/&lt;/code&gt;，以此類推。&lt;/p&gt;
&lt;p&gt;接著我們繼續往下看，可以看到放了一列不同的天氣 &lt;code&gt;string[] Summaries&lt;/code&gt;，還有一些屬性與使用依賴注入的建構子範例 &lt;code&gt;WeatherForecastController()&lt;/code&gt;，這個部份我們在後續的章節會再說明。&lt;/p&gt;
&lt;p&gt;在建構式之後，我們會看到 &lt;code&gt;Get()&lt;/code&gt; 方法，方法名稱並沒有什麼太大的關係，這邊要注意的重點在於：&lt;strong&gt;上面也掛了 &lt;code&gt;HttpGet&lt;/code&gt; 的屬性，這個部份用來制定我們該方法所對應的 HTTP Method，也就是我們前面 &lt;a href=&#34;#%E9%97%9C%E6%96%BC-restful-%E8%88%87-http-method&#34;&gt;Restful 小節&lt;/a&gt; 提過的「符合 HTTP 語意」所要處理的部分，例如 &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt; 等等，就可以用 &lt;code&gt;[HttpGet]&lt;/code&gt;, &lt;code&gt;[HttpPost]&lt;/code&gt; 等屬性來標示&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最後這個 &lt;code&gt;Get()&lt;/code&gt; 方法會隨機丟回天氣跟氣溫。現在就讓我們實際來呼叫看看吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：由於本系列還沒教到使用 Postman 或 Thunder client 這類直接呼叫 API 的方便小工具，所以呼叫的部分會使用瀏覽器或命令列進行示範，已經會使用這類工具的朋友可以用自己順手的工具嘗試就好囉。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在讓我們直接執行看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/61SgLPf.webp&#34;
  alt=&#34;&#34;width=&#34;97&#34; height=&#34;23&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;由於 &lt;code&gt;Properties/launchSettings.json&lt;/code&gt; 裡有設定了 &lt;code&gt;launchUrl&lt;/code&gt; 就是 &lt;code&gt;weatherforecast&lt;/code&gt;，所以我們應該會直接看到它幫忙打開瀏覽器，並取得（&lt;code&gt;GET&lt;/code&gt;）了 &lt;code&gt;https://localhost:{yourIISPort}/weatherforecast&lt;/code&gt; 的結果：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4jl7qvd.webp&#34;
  alt=&#34;&#34;width=&#34;478&#34; height=&#34;548&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：如果瀏覽器打開沒有自動排版而是一整陀的朋友，可以先去裝個 &lt;a href=&#34;https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc&#34;&gt;JsonView&lt;/a&gt; 來保護眼睛。安裝前後差異可以參見 &lt;a href=&#34;https://igouist.github.io/post/2020/05/jsonview&#34;&gt;Json View —— 用 Chrome 打開 Json 的正確方式&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;那如果是要用 Powershell 呼叫 API 的朋友，可以使用 &lt;code&gt;Invoke-RestMethod&lt;/code&gt; 來進行呼叫，再用 &lt;code&gt;ConvertTo-Json&lt;/code&gt; 轉換成比較好讀的格式，例如：&lt;code&gt;Invoke-RestMethod https://localhost:{yourIISPort}/weatherforecast | ConvertTo-Json&lt;/code&gt;。注意 yourIISPort 這兒是你啟動後的 port 號，像是 &lt;code&gt;https://localhost:44304/weatherforecast&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/QSp7VRr.webp&#34;
  alt=&#34;&#34;width=&#34;789&#34; height=&#34;970&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：Linux 的朋友就直接使用 Curl 來打就行了唄。另外，上面的 Powershell 語法特別感謝這篇 &lt;a href=&#34;https://blog.poychang.net/using-powershell-call-http-request/&#34;&gt;使用 PowerShell 呼叫 Web API 請求&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;到這邊我們就做完了簡單的認識，也確認我們新建的專案確實好好活著囉。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;動手實作-crud&#34;&gt;動手實作 CRUD&lt;/h3&gt;
&lt;p&gt;既然我們前面已經去逛過預設的 &lt;code&gt;WeatherForecastController&lt;/code&gt; 了，現在就讓我們來自己建一個吧。&lt;/p&gt;
&lt;p&gt;沒有脈絡就做不了事，先讓我們來訂一個情境：這是一個卡片對戰遊戲的卡片管理功能，一張卡片包含：卡片編號（ID）、卡片名稱和卡片敘述三個欄位。&lt;/p&gt;
&lt;p&gt;因為我這個人喜歡整理、愛好整潔（？）這邊就先讓我們新增一個 &lt;code&gt;Models&lt;/code&gt; 資料夾，用來存放卡片的類別&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Zoci28m.webp&#34;
  alt=&#34;&#34;width=&#34;665&#34; height=&#34;473&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;建立 &lt;code&gt;Models&lt;/code&gt; 資料夾之後，在 &lt;code&gt;Models&lt;/code&gt; 裡面新增 &lt;code&gt;Card.cs&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/EeAPqZv.webp&#34;
  alt=&#34;&#34;width=&#34;937&#34; height=&#34;655&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在應該會是這個樣子：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/b6hcSaw.webp&#34;
  alt=&#34;&#34;width=&#34;232&#34; height=&#34;171&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們打開 &lt;code&gt;Card.cs&lt;/code&gt;，加上卡片的各個欄位：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片編號&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Id { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片描述&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;完成卡片類別的建立之後，讓我們前往 &lt;code&gt;Controller&lt;/code&gt;，新建一個 &lt;code&gt;CardController.cs&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XRbNmT4.webp&#34;
  alt=&#34;&#34;width=&#34;246&#34; height=&#34;95&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們打開 &lt;code&gt;CardController.cs&lt;/code&gt;。這個類別裡面現在應該是空空如也：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 啥也沒有&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們就開始逐步施工吧（這邊的步驟可以去隔壁抄 &lt;code&gt;WeatherForecastController.cs&lt;/code&gt; 也 OK 啦）&lt;/p&gt;
&lt;p&gt;首先，我們要先繼承 &lt;code&gt;ControllerBase&lt;/code&gt; 取得控制器該有的方法和成員。接著，我們要加上 &lt;code&gt;[ApiController]&lt;/code&gt; 的屬性給這個類別，讓他知道他現在負責搞 API 了。&lt;/p&gt;
&lt;p&gt;如果過程中跑出紅線的話，就按一下燈泡或用 &lt;code&gt;Alt + Enter&lt;/code&gt; 把該 using 的東西給 using 進來。現在應該會像這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 啥也沒有&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們加上 &lt;code&gt;Route&lt;/code&gt;，因為我們按照資源下去設計的話，&lt;code&gt;Route&lt;/code&gt; 多半也是 &lt;code&gt;/card&lt;/code&gt; 這樣子的路徑，因此我們繼續使用 &lt;code&gt;[Route(&amp;quot;[controller]&amp;quot;)]&lt;/code&gt; 就可以了。如果各位要做的 API 資源在這邊需要客製化，再自己改成想要的字串即可。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 啥也沒有&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊就讓我們先建立一個 &lt;code&gt;static&lt;/code&gt; 並且為空的 &lt;code&gt;IEnumerable&amp;lt;Card&amp;gt;&lt;/code&gt; 的私有成員，用來驗證我們後續開的功能是否可以正常運作&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：我們會在下一個章節把這部分更改為連線資料庫去變更真正的資料，這邊就先用私有成員假裝一下唄。&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用的資料集合&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; List&amp;lt;Card&amp;gt; _cards = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Card&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣事前工作已經準備完畢了，讓我們來加上操作方法吧！&lt;/p&gt;
&lt;p&gt;首先先來一個查詢所有卡片的 Function，同樣用 &lt;code&gt;[HttpGet]&lt;/code&gt; 來標明這是個 &lt;code&gt;GET&lt;/code&gt; 方法，並且&lt;strong&gt;因為我們沒有特別指定路由，所以目前就是接著類別 &lt;code&gt;CardController&lt;/code&gt; 的 &lt;code&gt;/card&lt;/code&gt;，也就是 &lt;code&gt;GET /card&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; List&amp;lt;Card&amp;gt; GetList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; _cards;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後讓我們加上一個單獨查詢單張卡片的方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card Get([FromRoute] &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; _cards.FirstOrDefault(card =&amp;gt; card.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以注意到我們用 &lt;code&gt;Route&lt;/code&gt; 來指定了這個方法的 URL，&lt;strong&gt;並且用 &lt;code&gt;{id}&lt;/code&gt; 的方式告訴 API 說這一格是參數 &lt;code&gt;int id&lt;/code&gt; 所在的位置&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這樣的話這個方法的 Route 就會變成 &lt;code&gt;GET /card/1&lt;/code&gt;（查詢 ID 為 1 的卡片） 這種感覺。實際使用的時候我們會很經常用 &lt;code&gt;{參數}&lt;/code&gt; 這種方法來把參數加入到 Route 並制定 Function 對應的 URL，以此達到符合 Restful 的感覺。&lt;/p&gt;
&lt;p&gt;另一個可以注意到的地方是&lt;strong&gt;我們在 Function 的參數上加上了 &lt;code&gt;[FromRoute]&lt;/code&gt; 的屬性來告訴 API 說 &lt;code&gt;int id&lt;/code&gt; 這個參數是來自於 Route 上的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;除了 &lt;code&gt;FromRoute&lt;/code&gt; 以外，還有 &lt;code&gt;GET&lt;/code&gt; 時很常用到的 &lt;code&gt;[FromQuery]&lt;/code&gt; 或舊版本的 &lt;code&gt;[FromUri]&lt;/code&gt;（指這個參數從 QueryString 也就是 ?a=1&amp;amp;b=2 那串裡面接收）、&lt;code&gt;POST&lt;/code&gt; 和其他狀況常用到的 &lt;code&gt;[FromBody]&lt;/code&gt;（指這個參數要從 Body 接收）等等，可以讓我們標明參數的來源。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充說明一下，雖然也有簡單型別預設從 Uri，複雜型別預設從 Body 等等貼心的設定，但個人認為還是盡量都標明出來，對自己和後續維護的人都會比較好一點…。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著讓我們加入新增卡片，還有編輯卡片的方法吧，在這之前我們先建立一個 &lt;code&gt;Parameter&lt;/code&gt; 資料夾，用來放一些傳入的參數，並且新增一個 &lt;code&gt;CardParameter&lt;/code&gt; 來當作我們新增和修改卡片的參數類別：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fiqBwZM.webp&#34;
  alt=&#34;&#34;width=&#34;179&#34; height=&#34;78&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片參數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 卡片描述&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;補充：雖然也有建立一個對應的類別，接著就只使用這個類別的作法，像是先前的 &lt;a href=&#34;https://igouist.github.io/post/2019/12/aspnet-connect-db/&#34;&gt;Asp.net MVC&lt;/a&gt;，我們就只使用一個對應資料表欄位的類別來完成 CRUD 全部的操作。放到這裡來說的話，也就是只用 &lt;code&gt;Card&lt;/code&gt; 類別，查詢也是用這個類別顯示，更新也是用這個類別當作參數。&lt;/p&gt;
&lt;p&gt;但有些情況的時候，我們會希望傳進來新增或修改的參數跟類別並不一致，例如當我們新增 &lt;code&gt;Card&lt;/code&gt; 的時候，並不需要 &lt;code&gt;Id&lt;/code&gt; 這個欄位；又或是有些資料表中有 &lt;code&gt;CreateTime&lt;/code&gt;、&lt;code&gt;UpdateTime&lt;/code&gt; 這類程式自動填入、顯示的時候才有意義的欄位，就不會在新增或更新的時候對外開放；又或者是分層架構這類每一層要求的欄位並不一樣的情況等等。這些時候，我們就會採取將參數，也就是 &lt;code&gt;Parameter&lt;/code&gt; 切分成一個單獨的類別進行管控。&lt;/p&gt;
&lt;p&gt;基本上就像即使是同一張表，我們也會根據顯示的狀況來製作成不同的 &lt;code&gt;ViewModel&lt;/code&gt; 一樣，我們也會根據傳入的狀況來決定 &lt;code&gt;Parameter&lt;/code&gt; 的範圍。這個部份我們會在分層架構的時候再介紹一次，現在只要大致有&lt;strong&gt;可以把「顯示用類別」和「參數用類別」拆成不同的類別來進行出入口管制&lt;/strong&gt;的概念就可以。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;建立完參數之後，我們就可以回到 &lt;code&gt;CardController&lt;/code&gt; 來加上新增和更新的方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert([FromBody] CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _cards.Add(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Id = _cards.Any() 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          ? _cards.Max(card =&amp;gt; card.Id) + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          : &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;// 臨時防呆，如果沒東西就從 0 開始&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Name = parameter.Name,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Description = parameter.Description
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpPut]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Update(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; targetCard = _cards.FirstOrDefault(card =&amp;gt; card.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (targetCard &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; NotFound();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    targetCard.Name = parameter.Name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    targetCard.Description = parameter.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊的 &lt;code&gt;[HttpPost]&lt;/code&gt;、&lt;code&gt;[HttpPut]&lt;/code&gt; 以及 &lt;code&gt;[FromRpute]&lt;/code&gt;、&lt;code&gt;[FromBody]&lt;/code&gt; 我們在前面都已經掌握了。但在這裡我們還是可以看到 &lt;code&gt;return Ok()&lt;/code&gt; 和 &lt;code&gt;return NotFound()&lt;/code&gt; 這兩個新朋友。&lt;/p&gt;
&lt;p&gt;聰明的朋友看 return 的方法名稱應該已經猜到了，這就是我們前面提過的 &lt;a href=&#34;#%E9%97%9C%E6%96%BC-http-status-code&#34;&gt;HTTP Status&lt;/a&gt;。當然前面查詢成功的時候和這邊的 &lt;code&gt;Ok()&lt;/code&gt; 一樣是 &lt;code&gt;200&lt;/code&gt; 的狀態，此外還有代表 &lt;code&gt;400&lt;/code&gt; 的 &lt;code&gt;BadRequest()&lt;/code&gt;、代表 &lt;code&gt;404&lt;/code&gt; 的 &lt;code&gt;NotFound()&lt;/code&gt; 等等。&lt;/p&gt;
&lt;p&gt;除此之外，我們也不是每次都會回傳 &lt;code&gt;IActionResult&lt;/code&gt;，可能也會是自訂的錯誤型別等等，所以也看過使用 &lt;code&gt;Response.StatusCode&lt;/code&gt; 來直接設定 Http Status Code 的做法，例如 &lt;code&gt;Response.StatusCode = 200;&lt;/code&gt; 之後再進行回傳的做法，各位再視情況使用吧。&lt;/p&gt;
&lt;p&gt;現在 CRUD 四大天王，我們已經只剩下 D 了，現在就來把刪除卡片的方法補上吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[HttpDelete]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Delete([FromRoute] &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _cards.RemoveAll(card =&amp;gt; card.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣四大天王（包含查詢列表通常是五個）就到齊了，現在的 &lt;code&gt;CardController&lt;/code&gt; 應該是長這樣的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CardController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 測試用的資料集合&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; List&amp;lt;Card&amp;gt; _cards = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Card&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; List&amp;lt;Card&amp;gt; GetList()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; _cards;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 查詢卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card Get([FromRoute] &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; _cards.FirstOrDefault(card =&amp;gt; card.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 新增卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Insert([FromBody] CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _cards.Add(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Id = _cards.Any() 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ? _cards.Max(card =&amp;gt; card.Id) + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                : &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;// 臨時防呆，如果沒東西就從 0 開始&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = parameter.Name,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Description = parameter.Description
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 更新卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;parameter&amp;#34;&amp;gt;卡片參數&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpPut]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Update(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromRoute]&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;        [FromBody]&lt;/span&gt; CardParameter parameter)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; targetCard = _cards.FirstOrDefault(card =&amp;gt; card.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (targetCard &lt;span style=&#34;color:#66d9ef&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; NotFound();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        targetCard.Name = parameter.Name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        targetCard.Description = parameter.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// 刪除卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;param name=&amp;#34;id&amp;#34;&amp;gt;卡片編號&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [HttpDelete]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;    [Route(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Delete([FromRoute] &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _cards.RemoveAll(card =&amp;gt; card.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後讓我們啟動來測試一下吧！&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：和上面同樣地，因為這邊還沒說明到 Postman 等測試軟體，所以直接使用 Powershell 進行示範，已經有慣用軟體，或是直接寫一個腳本出來接的朋友，請用自己方便順手的測試方法去呼叫就好囉。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;讓我們打開 Powershell，繼續使用 &lt;code&gt;Invoke-RestMethod&lt;/code&gt; 來呼叫 API 試試看，別忘了 Port 要換成你啟動專案時 IIS 掛上去的 Port&lt;/p&gt;
&lt;p&gt;首先讓我們新增一張卡片：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod https&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;//localhost&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;44304&lt;/span&gt;/card `
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -Method &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;POST&amp;#39;&lt;/span&gt; `
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -Headers @{ &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;; } `
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -Body &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;{&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;mycard&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;description&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;sample card&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們查詢看看所有卡片，看看新增的卡片在不在：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod https&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;//localhost&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;44304&lt;/span&gt;/card | ConvertTo-Json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IvHYdZR.webp&#34;
  alt=&#34;&#34;width=&#34;781&#34; height=&#34;512&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;既然卡片已經存在了，讓我們試試看編輯，把「我的卡片」改成「我們的卡片」，&lt;del&gt;打倒富農份子&lt;/del&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod https&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;//localhost&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;44304&lt;/span&gt;/card/&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; `
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -Method &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;PUT&amp;#39;&lt;/span&gt; `
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -Headers @{ &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;; } `
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -Body &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;{&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;ourcard&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;description&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;sample card&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;`&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;並且用查詢單張卡片的方式來查詢卡片資料：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod https&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;//localhost&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;44304&lt;/span&gt;/card/&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; | ConvertTo-Json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/FjFHdSt.webp&#34;
  alt=&#34;&#34;width=&#34;740&#34; height=&#34;401&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果編輯的卡片不存在，會噴出 404：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/j6Dom6l.webp&#34;
  alt=&#34;&#34;width=&#34;1267&#34; height=&#34;395&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最後讓我們來試試刪除：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod https&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;//localhost&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;44304&lt;/span&gt;/card/&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; `
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; -Method &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;DELETE&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後同樣使用查詢全部，應該要沒有任何卡片了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Invoke-RestMethod https&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;//localhost&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;44304&lt;/span&gt;/card | ConvertTo-Json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/0WNEkAm.webp&#34;
  alt=&#34;&#34;width=&#34;716&#34; height=&#34;375&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;到這邊就確認我們的 API 服務（也就是基本的 CRUD）已經 ON 起來啦！&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;這邊我們紀錄了一些會用到的 HTTP 基礎知識，並用 Asp.net Core 的 Web API 範本新建了一個 API 服務，也加入了自己設定的 CRUD。&lt;/p&gt;
&lt;p&gt;但要特別注意，雖然我們對外的開口已經建起來了，但這時候的 Card 還只是個用 static 變數假裝的空殼，只要站台重啟就會消失了。&lt;/p&gt;
&lt;p&gt;正所謂「&lt;strong&gt;沒有連到資料庫的 CRUD，就像是沒有加醬汁的料理！&lt;/strong&gt;」，我們在下一集就要來把我們的 Api 服務連接到資料庫啦！&lt;/p&gt;
&lt;p&gt;那麼，我們下次見～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2021.05.08 補充：&lt;/p&gt;
&lt;p&gt;範本預設的 &lt;code&gt;WeatherForecast.cs&lt;/code&gt; 和 &lt;code&gt;WeatherForecastController&lt;/code&gt; 在將來的文章將不會再用到，繼續下一篇實作之前可以先刪除囉～&lt;/p&gt;
&lt;p&gt;有刪除的朋友記得要去 &lt;code&gt;launchSettings.json&lt;/code&gt; 把 &lt;code&gt;launchUrl&lt;/code&gt; 改成 card，下次啟動才會直接到我們這次新加的 CardController 呦！&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;本系列文章&#34;&gt;本系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@hulitw/learning-tcp-ip-http-via-sending-letter-5d3299203660&#34;&gt;從傳紙條輕鬆學習基本網路概念 - huli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@hulitw/ramen-and-api-6238437dc544&#34;&gt;從拉麵店的販賣機理解什麼是 API - huli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/codingbar/api-%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E9%BA%BC-%E7%94%A8%E7%99%BD%E8%A9%B1%E6%96%87%E5%B8%B6%E4%BD%A0%E8%AA%8D%E8%AD%98-95f65a9cfc33&#34;&gt;API 到底是什麼？ 用白話文帶你認識 - BAR 主特調&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/azure/architecture/best-practices/api-design&#34;&gt;Web API 設計 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://progressbar.tw/posts/53&#34;&gt;休息(REST)式架構? 寧靜式(RESTful)的Web API是現在的潮流？ - 進度條&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tw.twincl.com/programming/*641y&#34;&gt;簡明 RESTful API 設計要點 - Twincl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10230223&#34;&gt;不做怎麼知道系列之Android開發者的30天後端養成故事 Day22 - 什麼是真正的 RESTful API? #RESTful API應該長什麼樣子?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/dotnet-core-webapi%E5%AF%A6%E4%BD%9C-4-restful-api%E4%BB%8B%E7%B4%B9/&#34;&gt;dotnet Core WebApi實作-4 RESTful API介紹 _ Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E8%90%AC%E4%BA%8B%E5%B1%8B%E9%98%BF%E6%B3%B0%E7%9A%84%E7%9F%A5%E8%AD%98%E6%B5%B7/cs-2-%E6%AF%8F%E6%AF%8F%E8%81%BD%E5%88%B0%E5%B7%A5%E7%A8%8B%E5%B8%AB%E8%AA%AA-api-%E6%89%80%E4%BB%A5%E5%88%B0%E5%BA%95%E4%BB%80%E9%BA%BC%E6%98%AF-api-%E5%95%8A-b155662425e&#34;&gt;CS #2 — 每每聽到工程師說 API，所以到底什麼是 API 啊？ - 萬事屋阿泰的知識海&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://notfalse.net/39/http-message-format&#34;&gt;HTTP/1.1 — 訊息格式 (Message Format) - NotFalse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/ttyy2985/http%E5%89%8D%E5%BE%8C%E7%AB%AF%E5%82%B3%E8%BC%B8%E6%B5%81%E7%A8%8B-8ee40ffca1bd&#34;&gt;HTTP 前後端傳輸流程 - ttyy2985&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/hobo-engineer/ricky%E7%AD%86%E8%A8%98-postman-%E5%B8%B8%E8%A6%8B%E7%9A%84-content-type-b17a75396668&#34;&gt;Postman 常見的 Content-type - hobo-engineer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://noob.tw/http-status-code/&#34;&gt;常見與不常見的 HTTP Status Code - Noob&amp;rsquo;s Space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2009/01/16/Web-developer-should-know-about-HTTP-Status-Code&#34;&gt;網頁開發人員應了解的 HTTP 狀態碼 - The Will Will Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2020/06/28/how_to_read_config_appsettings_json_via_net_core_31&#34;&gt;如何讀取 AppSettings.json 組態設定檔 - 余小章&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.poychang.net/visual-studio-launch-settings-iis-express-iis-project-executable/&#34;&gt;launchSettings.json 的 commandName 是做什麼用的？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.johnwu.cc/article/ironman-day02-asp-net-core-application-lifetime.html&#34;&gt;ASP.NET Core 2 系列 - 程式生命週期 (Application Lifetime) - John Wu&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.poychang.net/using-powershell-call-http-request/&#34;&gt;使用 PowerShell 呼叫 Web API 請求&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE&#34;&gt;超文本傳輸協定 - 維基百科&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Visual Studio: 在同一個檔案分割視窗</title>
      <link>https://igouist.github.io/post/2021/05/visual-studio-split-window-in-one-file/</link>
      <pubDate>Sun, 02 May 2021 10:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/05/visual-studio-split-window-in-one-file/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/LEB9mUy.webp&#34;
  alt=&#34;&#34;width=&#34;748&#34; height=&#34;667&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當我們遇到比阿嬤的裹腳布還臭還長的類別時，常常會發生「需要一邊確認 Public 的 Function，但它用到的 Private Function 卻遠在天邊」，或是「SQL 字串／字串常數等等另外宣告在檔案最上端，導致瀏覽邏輯到一半的時候還要來回跳」的狀況。&lt;/p&gt;
&lt;p&gt;在 &lt;a href=&#34;https://igouist.github.io/post/2021/03/visual-studio-bookmark&#34;&gt;上一篇&lt;/a&gt; 我們分享過用書籤的方式來記錄兩個地方來回飛躍，但如果是要互相比對或理解流程等等時候，就比不上分割視窗來的方便。&lt;/p&gt;
&lt;p&gt;在 Visual Studio 用分割視窗的方式開啟不同的檔案，相信大家都已經駕輕就熟，尤其用過 Visual Studio 來進行 Merge 的朋友一定對這樣的排版不陌生。但是你知道就算&lt;strong&gt;對同一個檔案，也可以使用分割視窗來同時編輯兩個地方嗎&lt;/strong&gt;？只需要動動滑鼠就可以囉！&lt;/p&gt;
&lt;p&gt;我們只需要將滑鼠移到卷軸上…&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/H8G3k1M.gif&#34;
  alt=&#34;&#34;width=&#34;206&#34; height=&#34;109&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;就成功分割出來啦！&lt;/p&gt;
&lt;p&gt;接著只要使用 F6 就可以在兩個視窗之間切換，同時編輯同個檔案的兩個地方囉！&lt;/p&gt;
&lt;p&gt;這邊也分享給大家，下次遇到這些狀況就試試唄！&lt;/p&gt;
&lt;p&gt;&lt;del&gt;謝謝把連到資料表的 Function 裡的 SQL 語法丟到地球彼端的前輩，讓我必須找這種技巧來用&lt;/del&gt;&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (1): 使用 Git 來進行版本控制吧</title>
      <link>https://igouist.github.io/post/2021/04/newbie-1-hello-git/</link>
      <pubDate>Mon, 05 Apr 2021 22:39:01 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/04/newbie-1-hello-git/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ojI91y9.webp&#34;
  alt=&#34;img&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這是俺整理公司新訓內容的第一篇文章，目標是&lt;strong&gt;整理 Git 相關的筆記&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#前言推薦資源&#34;&gt;前言、推薦資源&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是-git&#34;&gt;什麼是 Git？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是分散式版本控制&#34;&gt;什麼是分散式版本控制？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#先告訴-git-我們是誰&#34;&gt;先告訴 Git 我們是誰&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#建立一個新的儲存庫git-init&#34;&gt;建立一個新的儲存庫（Git Init）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#把檔案加到-git-的追蹤目標git-add&#34;&gt;把檔案加到 Git 的追蹤目標（Git Add）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#提交變更git-commit&#34;&gt;提交變更（Git Commit）&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#git-commit-的訊息該怎麼寫&#34;&gt;Git Commit 的訊息該怎麼寫？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#git-commit-的時機&#34;&gt;Git Commit 的時機？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#git-commit-和-add-的-combo-技&#34;&gt;Git Commit 和 Add 的 Combo 技&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#加入-gitignore-來忽略指定檔案&#34;&gt;加入 .gitignore 來忽略指定檔案&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#查詢-commit-紀錄git-log&#34;&gt;查詢 Commit 紀錄（Git Log）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#查詢兩個-commit-之間的差異git-diff&#34;&gt;查詢兩個 Commit 之間的差異（Git Diff）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#git-的-head-是什麼&#34;&gt;Git 的 HEAD 是什麼？&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#git-revert讓我們往回走一步&#34;&gt;Git Revert：讓我們往回走一步&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#git-reset讓我們搭上時光機&#34;&gt;Git Reset：讓我們搭上時光機&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#git-的分支branch是什麼&#34;&gt;Git 的分支（Branch）是什麼？&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#新建分支branch與切換分支checkout&#34;&gt;新建分支（branch）與切換分支（checkout）&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#git-stash讓我們快速存個檔&#34;&gt;Git Stash：讓我們快速存個檔&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#git-merge讓我們合併兩條分支吧&#34;&gt;Git Merge：讓我們合併兩條分支吧&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什麼是衝突conflict&#34;&gt;什麼是衝突（Conflict）？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#git-rebase讓我們移花接木&#34;&gt;Git Rebase：讓我們移花接木&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#我們現在有哪些分支git-branch要怎麼刪除分支&#34;&gt;我們現在有哪些分支（Git Branch）？要怎麼刪除分支？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#認識-git-的斷頭detached-head&#34;&gt;認識 Git 的斷頭（detached HEAD）&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#關於-git-的分支策略&#34;&gt;關於 Git 的分支策略&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#什麼是-git-的遠端儲存庫remote要怎麼把變更推送push到遠端儲存庫&#34;&gt;什麼是 Git 的遠端儲存庫（Remote）？&lt;br/&gt;要怎麼把變更推送（Push）到遠端儲存庫？&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#要怎麼從-git-的遠端儲存庫拿到變更認識擷取fatch提取pull&#34;&gt;要怎麼從 Git 的遠端儲存庫拿到變更？認識擷取（Fatch）、提取（Pull）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#使用-git-clone-直接把遠端儲存庫的-repo-抓下來&#34;&gt;使用 Git Clone 直接把遠端儲存庫的 Repo 抓下來&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#認識-git-的提取要求pull-request-pr&#34;&gt;認識 Git 的提取要求（pull request, PR）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小結&#34;&gt;小結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#本系列文章&#34;&gt;本系列文章&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;前言推薦資源&#34;&gt;前言、推薦資源&lt;/h2&gt;
&lt;p&gt;說來慚愧，前陣子 PTT 和臉書社團都有討論到相關科系畢業卻不會 Git 會不會太誇張，我正是畢業之後才開始用 Git 的那類人囧，相信像我一樣的人並不少，因此這個系列就決定從「&lt;strong&gt;新訓時學到的 Git 的基本操作&lt;/strong&gt;」開始記錄。&lt;/p&gt;
&lt;p&gt;開始之前先感謝公司前輩和完善的新手教學，還有第一天就先學 Git 的優良傳統。另外，也感謝相當多優秀的 Git 學習資源，說明得也更為詳細深入，想好好了解 Git 的朋友也可以逛逛，這邊就先推薦一波：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/&#34;&gt;為你自己學 Git&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;對新手非常友善。網站點進去後往下拉，可以看到大部分章節都能免費看，佛！&lt;/li&gt;
&lt;li&gt;最有價值的是裡面的各種狀況題。畢竟當你用 Git 不只需要基本操作的時候，呃，祝你好運&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://backlog.com/git-tutorial/tw/&#34;&gt;連猴子都能懂的 Git 入門指南&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;從入門到進階篇跟過一次的話，基本操作就沒有問題了&lt;/li&gt;
&lt;li&gt;圖解讓人很好理解，而且在教學的實作部分會提供儲存庫讓你下載實作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/category/Git&#34;&gt;黑暗執行緒的 Git 分類文章&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;同場加映：&lt;a href=&#34;https://blog.darkthread.net/blog/my-git-cheatsheet/&#34;&gt;黑暗執行緒的 Git 指令筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;黑大出品，品質保證&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://learngitbranching.js.org/?locale=zh_TW&#34;&gt;Learn Git Branching&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;用遊戲通關的方式認識 Git，對於一些分支的概念會很有幫助&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/users/20004901/ironman/525&#34;&gt;30 天精通 Git 版本控管&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git-scm.com/book/zh-tw/v2&#34;&gt;Pro Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下來我們就從認識 Git 開始吧！&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是-git&#34;&gt;什麼是 Git？&lt;/h3&gt;
&lt;p&gt;你發生過以下狀況嗎？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;從沒做過版本控制，結果突然要改回前一版，不知所措&lt;/li&gt;
&lt;li&gt;使用資料夾／壓縮檔板控
&lt;ul&gt;
&lt;li&gt;20201201.rar, 20201215_v2.rar, 20201215_首頁.rar&amp;hellip;&amp;hellip;&lt;/li&gt;
&lt;li&gt;空間越吃越兇，東西越來越雜，事情越想越不對勁，但是不敢刪除&lt;/li&gt;
&lt;li&gt;其實不知道每一份實際上改了哪裡，要復原某一段的時候要找半天，不如直接重寫一段&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;團隊合作／分組報告，各自負責一個區域，結果複製來複製去組不起來，不只需要看眼科，修 BUG 還比寫的時間還多&lt;/li&gt;
&lt;li&gt;看到一段程式碼
&lt;ul&gt;
&lt;li&gt;完全不知道為什麼要這樣寫&lt;/li&gt;
&lt;li&gt;或是氣到要死，抓不到戰犯&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那麼，你很有可能需要 Git！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Git 是一套分散式的版本控制，就像是打電動時的存檔&lt;/strong&gt;。讓我們可以在面臨重要選擇的時候存檔、打王之前存檔、打贏的時候也存個檔。當然，像是那種有多劇情多結局的遊戲，也可以針對不同路線各自存檔。&lt;/p&gt;
&lt;p&gt;同時它也支援雲端存檔，你可以在電腦上存個檔，然後有網路的時候就丟上去雲端備份一下。而這個雲端備份是共用的，所以你可以跟朋友一起玩同一款遊戲，各自攻略不同的 BOSS，再把存檔和朋友互相交流交流，合成一個有兩份戰利品的存檔。&lt;/p&gt;
&lt;p&gt;這些功能在 Git 有著聽起來比較厲害的名字，例如認可（Commit）、分支（Branch）、分散式、合併（Merge）等等。我們後續再慢慢了解它們。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是分散式版本控制&#34;&gt;什麼是分散式版本控制？&lt;/h3&gt;
&lt;p&gt;現在我們已經有個大致上的印象了，但 Git 還有更多特色，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;免費！開源！&lt;/li&gt;
&lt;li&gt;讀檔存檔的速度很快！&lt;/li&gt;
&lt;li&gt;分散式！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前面兩項比較好理解，我們這邊說明一下什麼是「分散式」的版本控制：&lt;/p&gt;
&lt;p&gt;以前的版本控制，例如 SVM，是採用「集中式」的版本控制：每次變更完要存個檔等等，都要連線到伺服器上進行處理，就像是好幾個人一起連線存取同一張資料表一樣。但是這樣遇到沒有網路的狀況就沒辦法讀檔存檔，或是變更的檔案很大就會等到天荒地老，這種對伺服器強依賴的狀況實在是有點兒不方便。&lt;/p&gt;
&lt;p&gt;分散式版本控制呢，則是&lt;strong&gt;每個人都有各自的完整一份資料&lt;/strong&gt;，你要存檔讀檔啥的都你自己電腦上弄就好了，只有必須和其他人交流的情況（例如想丟上去雲端存檔了，寫完了要合併了）才需要透過網路來處理，這樣子日常做事起來就快上不少。&lt;/p&gt;
&lt;p&gt;也因為每個人都有一份，某台機器掛掉就導致整份程式碼不見的狀況少了很多，變更歷史也不容易竄改。同時更發展出強大的分支操作，和一些工作流程等等，是相當靈活的版本控制方式。&lt;/p&gt;
&lt;p&gt;關於版本控制方式的比較也可以參考這兩則文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.liaoxuefeng.com/wiki/896043488029600/896202780297248&#34;&gt;集中式vs分布式 - 廖雪峰的官方网站&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://iissnan.com/progit/html/zh-tw/ch1_1.html&#34;&gt;關於版本控制 - 開始 - Pro Git 繁體中文版 (iissnan.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;現在我們知道了 Git 是一個分散式的版本控制軟體，幫助我們做一些存檔讀檔同步的動作。接著就讓我們開始來操作看看吧！&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;先告訴-git-我們是誰&#34;&gt;先告訴 Git 我們是誰&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：你可能需要先 &lt;a href=&#34;https://git-scm.com/downloads&#34;&gt;安裝 Git&lt;/a&gt;。&lt;br/&gt;安裝過程相當簡單，通常只需要下一步即可。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先我們得先確認 Git 已經準備好了，打開我們的命令視窗（Powershell 或是 CMD 之類的），輸入 &lt;code&gt;git --version&lt;/code&gt;，你應該能夠看見 Git 的版本資訊&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/YUKIdit.webp&#34;
  alt=&#34;&#34;width=&#34;471&#34; height=&#34;179&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;另外，也可以輸入 &lt;code&gt;git --help&lt;/code&gt; 來查詢可用的指令。或是加上想用的指令，例如 &lt;code&gt;git --help clone&lt;/code&gt; 就會開啟該指令的文檔，臨時要確認指令和參數的時候相當有用。&lt;/p&gt;
&lt;p&gt;Git 現在已經有相當多的 GUI 可以使用，真的很多。我個人在家的時候是使用 &lt;a href=&#34;https://desktop.github.com/&#34;&gt;GitHub Desktop&lt;/a&gt;、在公司大多時候使用 Visual Studio 內建的 Git 工具。此外，也見過朋友和同事使用 &lt;a href=&#34;https://www.gitkraken.com/&#34;&gt;GitKraken&lt;/a&gt;、&lt;a href=&#34;https://git-fork.com/&#34;&gt;Fork&lt;/a&gt; 等等。關於這些 Git 的 GUI，可以參考官方整理的 &lt;a href=&#34;https://git-scm.com/downloads/guis&#34;&gt;GUI Clients&lt;/a&gt; 頁面。&lt;/p&gt;
&lt;p&gt;但由於我個人忘記指令的狀況頗為嚴重，而且也不是每個環境都有 GUI 可以用。因此這篇有關 Git 的部分將會以 CLI 指令為主進行紀錄。使用 GUI 介面的朋友也不用擔心，現在的介面都做得很精簡，&lt;s&gt;而且這篇也寫得很淺，&lt;/s&gt;很簡單就能找到各指令對應的操作。&lt;/p&gt;
&lt;p&gt;確認我們已經有 Git 之後，接著就必須先跟 Git 說我們是誰、信箱是什麼：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git config --global user.name &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I am INEVITABL&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git config --global user.email &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thanos@Gemmai1.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就會把設定好的名稱和信箱存到 config 裡面。如果要查看現在有的 config，可以輸入&lt;br/&gt; &lt;code&gt;git config --list&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;另外也還能指定編譯器、新增別名等等，可以參照 &lt;a href=&#34;https://gitbook.tw/chapters/config/convenient-settings.html&#34;&gt;其它方便的設定 - 為你自己學 Git&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;由於 Git 是用這組名稱和信箱進行辨別，因此在 Github 上就可以做一些很酷的事。例如假冒人家和防止人家假冒（？），有興趣的可以看看：&lt;a href=&#34;https://medium.com/starbugs/how-to-fake-the-author-of-git-commit-f44453b70afc&#34;&gt;用 Git 這麼久了，你知道 commit 是可以偽造的嗎&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;順便打個廣告：好奇我的 Powershell 長得跟你的「有點不一樣」的朋友，可以參考本部落格的另一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/08/powershell-beauty/&#34;&gt;Powershell 美化作戰&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;建立一個新的儲存庫git-init&#34;&gt;建立一個新的儲存庫（Git Init）&lt;/h2&gt;
&lt;p&gt;現在讓我們從建立一個新的儲存庫開始。現在讓我們新增一個資料夾（在這邊我取名叫做 &lt;code&gt;hello-git&lt;/code&gt;）當作這篇 Git 紀錄的遊樂場：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hPLoleB.webp&#34;
  alt=&#34;&#34;width=&#34;278&#34; height=&#34;239&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;題外話：既然都打指令了，也可以試試來建立資料夾 &lt;br/&gt;Powershell 用 &lt;code&gt;New-Item C:\hello-git -ItemType &amp;quot;directory&amp;quot;&lt;/code&gt; &lt;br/&gt;隔壁棚 Linux 請用 &lt;code&gt;mkdir&lt;/code&gt; 來試試。&lt;br/&gt;不過基於懶惰，上面的示範是滑鼠右鍵建立的，耶嘿&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著讓我們先移動過去資料夾：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd C:\hello-git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;並且&lt;strong&gt;使用 &lt;code&gt;git init&lt;/code&gt; 將 Git 初始化&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WIckbA1.webp&#34;
  alt=&#34;&#34;width=&#34;568&#34; height=&#34;221&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如此一來，這個資料夾就成為了「&lt;strong&gt;工作區（Working directory）&lt;/strong&gt;」，也就是「歸我 Git 管啦！」的意思&lt;/p&gt;
&lt;p&gt;同時，我們也能在原本的 hello-git 資料夾中，發現多了一個 &lt;code&gt;.git&lt;/code&gt; 的隱藏檔案：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3hgT4eP.webp&#34;
  alt=&#34;&#34;width=&#34;258&#34; height=&#34;130&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這個 &lt;code&gt;.git&lt;/code&gt; 就是用來幫我們處理一堆版本控制工作的地方，也叫做&lt;br/&gt;「&lt;strong&gt;儲存庫（Repository）&lt;/strong&gt;」。裡面會放一些設定值、已經確認變更的文件等等。&lt;/p&gt;
&lt;p&gt;現在讓我們來確認一下這個工作區的狀況，&lt;code&gt;git status&lt;/code&gt; 將會列出當前的狀態，這將會是我們很常使用的指令。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5XwsruS.webp&#34;
  alt=&#34;&#34;width=&#34;665&#34; height=&#34;262&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到我們現在在 Master 分支，並且還沒有 Commit 任何東西。這兩個部分我們等等就會說明，現在就讓我們按照它的提示，來把檔案丟進去給 Git 試試吧。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;把檔案加到-git-的追蹤目標git-add&#34;&gt;把檔案加到 Git 的追蹤目標（Git Add）&lt;/h2&gt;
&lt;p&gt;我們先到 hello-git 資料夾裡，新增一個 &lt;code&gt;A.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/X8M9I67.webp&#34;
  alt=&#34;&#34;width=&#34;277&#34; height=&#34;137&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且加入一些內容，例如「Hello!」&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/erpTd0w.webp&#34;
  alt=&#34;&#34;width=&#34;272&#34; height=&#34;103&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在讓我們再次使用 &lt;code&gt;git status&lt;/code&gt; 觀察一下&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/J7ByoFh.webp&#34;
  alt=&#34;&#34;width=&#34;826&#34; height=&#34;398&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到 Git 已經發現 A.txt 的存在了，但它也告訴我們，它還沒有把 A.txt 放在 &lt;s&gt;眼裡&lt;/s&gt; 追蹤目標中&lt;/p&gt;
&lt;p&gt;我們要用 &lt;code&gt;git add&lt;/code&gt; 指令，Git 才會把這個變更納入這次的動作中：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add A.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fFlY2rK.webp&#34;
  alt=&#34;&#34;width=&#34;634&#34; height=&#34;417&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在可以看到我們新增檔案這個動作已經被 Git 捕捉到了。&lt;/p&gt;
&lt;p&gt;前面有提過，&lt;strong&gt;我們在 &lt;code&gt;git init&lt;/code&gt; 之後，當下的資料夾就會變成「工作區」，而 .git 則會成為「儲存庫」。在這兩者之間，還會有一層「暫存區（Staging Area）」&lt;/strong&gt;（有些朋友會叫做「索引區（index）」，對象是一樣的）。&lt;/p&gt;
&lt;p&gt;這三者之間的工作流程就像是一條工廠輸送帶：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;工作區（Working directory）→ 暫存區（Staging Area）→ 儲存庫（Repository）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而這個 &lt;strong&gt;&lt;code&gt;add&lt;/code&gt;&lt;/strong&gt; 加入檔案的過程，其實就是將我們 &lt;strong&gt;在工作區所做的變更，加入到暫存區（Staging Area）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以我們新增檔案、變更內容等等，其實都要跟 Git 用 &lt;code&gt;add&lt;/code&gt; 指令打聲招呼，說「我有動這個哦，幫我看著一下」，Git 才會把這些變更的對象放到暫存區裡，等待後續丟到儲存庫的動作。&lt;/p&gt;
&lt;p&gt;那有些朋友可能就會問啦：我工作的時候處理的檔案一定很多個啊，每個都要 add 豈不是累死？&lt;/p&gt;
&lt;p&gt;不用擔心，當有多個檔案要 &lt;code&gt;add&lt;/code&gt; 的時候，我們可以加上參數 &lt;code&gt;-A&lt;/code&gt;，也就是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add -A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或是&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add --all
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣就會直接把所有變更抓進來囉。&lt;/p&gt;
&lt;p&gt;當然，懶還要更懶，事實上現在的 GUI 工具，例如我接觸的 GitHub Desktop、Visual Studio 等等，其實都會自動幫忙 Add 了，真是貼心。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/dARJCu3.webp&#34;
  alt=&#34;&#34;width=&#34;483&#34; height=&#34;135&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當我們把變更從工作區用 add 丟到暫存區之後，要怎麼再從暫存區丟進儲存庫呢？這時候就要使用 &lt;code&gt;Commit&lt;/code&gt; 了！&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;提交變更git-commit&#34;&gt;提交變更（Git Commit）&lt;/h2&gt;
&lt;p&gt;完成了一項功能？　Commit！&lt;br/&gt;解了一個ＢＵＧ？　Commit！&lt;br/&gt;下班了？　Commit！&lt;br/&gt;地震了？　Commit！&lt;/p&gt;
&lt;p&gt;如果你有在使用 Git，Commit 絕對是你使用最多次的功能。&lt;strong&gt;當我們 Commit 之後，暫存區的變更就會寫入儲存庫，到這個步驟我們才真正地存檔成功。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;現在讓我們來完成這次變更吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Add A.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/riSISaf.webp&#34;
  alt=&#34;&#34;width=&#34;607&#34; height=&#34;229&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有看到 ~ file changed 就代表我們已經成功 Commit，把變更存進儲存庫囉！&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;git-commit-的訊息該怎麼寫&#34;&gt;Git Commit 的訊息該怎麼寫？&lt;/h3&gt;
&lt;p&gt;這邊要特別提的是 &lt;code&gt;-m &amp;quot;Add A.txt&amp;quot;&lt;/code&gt; 這個部分。&lt;code&gt;-m&lt;/code&gt; 就是 Message 的 m（好順口），是用來輸入本次 Commit 的訊息，雖然可以省略，但&lt;strong&gt;強烈建議 Commit 的時候都一定要加上訊息！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;實際想想就能理解了，既然 Git 是可以讓你隨時存檔讀檔的工具，那麼假設你看到這一排存檔：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存檔&lt;/li&gt;
&lt;li&gt;存檔&lt;/li&gt;
&lt;li&gt;aaa&lt;/li&gt;
&lt;li&gt;a&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;和這一排存檔：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;結局選項前&lt;/li&gt;
&lt;li&gt;結局選項１&lt;/li&gt;
&lt;li&gt;結局選項２&lt;/li&gt;
&lt;li&gt;決戰前&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;哪一組比較能快速知道要讀取哪個檔案呢？&lt;/p&gt;
&lt;p&gt;Git Commit Message 也是一樣的道理。&lt;/p&gt;
&lt;p&gt;好的訊息可以快速了解每個版本的變更和背後原因，甚至讓後續接手的人（通常也就是幾天或幾個月後的自己）能迅速地掌握狀況。因此現在大多數的 Git 工具都會要求必須輸入 Commit 訊息，畢竟「訊息一條勝造七級浮屠」，不可不慎哪。&lt;/p&gt;
&lt;p&gt;因此，這邊強烈推薦這篇 &lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/05/commit-commit-commit-why-what-commit.html&#34;&gt;Git Commit Message 這樣寫會更好，替專案引入規範與範例&lt;/a&gt;，內文用實際案例和 AngularJS 團隊的 Git Message 規範說明了好的 Message 該如何處理，相當清楚明瞭。&lt;/p&gt;
&lt;p&gt;趁開始學習 Git 的時候就培養好的 Commit 習慣，將來一定都會派上用場。現在我也和同事一起嘗試著這套作法，畢竟通常救到的都是未來的自己嘛，哈哈。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於 Commit Message 的部分，可以參照這幾篇呦：&lt;br/&gt;
。 &lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/05/commit-commit-commit-why-what-commit.html&#34;&gt;Git Commit Message 這樣寫會更好，替專案引入規範與範例&lt;/a&gt;&lt;br/&gt;
。 &lt;a href=&#34;https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/&#34;&gt;如何寫一個 Git Commit Message | louie_lu&amp;rsquo;s blog&lt;/a&gt;&lt;br/&gt;
。 &lt;a href=&#34;http://blog.fourdesire.com/2018/07/03/%e6%92%b0%e5%af%ab%e6%9c%89%e6%95%88%e7%9a%84-git-commit-message/&#34;&gt;撰寫有效的 Git Commit Message&lt;/a&gt;&lt;br/&gt;
。 &lt;a href=&#34;https://heidiliu2020.github.io/git-commit-message/&#34;&gt;[學習筆記] 如何撰好的 Git Commit Message - Heidi&amp;rsquo;s Blog&lt;/a&gt;&lt;br/&gt;
看完之後也可以試著制定自己的格式，其實也蠻有趣的呢（當然還是要以方便順手為主啦～）&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果不小心打錯 Commit Message 之類的怎麼辦？&lt;/p&gt;
&lt;p&gt;像我這種錯字狂魔，幾乎兩三天就會打錯字就按下去。這時候我們就會需要 &lt;code&gt;--amend&lt;/code&gt;！&lt;/p&gt;
&lt;p&gt;可以參照：&lt;a href=&#34;https://gitbook.tw/chapters/using-git/amend-commit1&#34;&gt;【狀況題】修改 Commit 紀錄 - 為你自己學 Git&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&#34;git-commit-的時機&#34;&gt;Git Commit 的時機？&lt;/h3&gt;
&lt;p&gt;另外，除了 Commit Message 以外，Commit 的時機和頻率也是時常被討論的議題。&lt;/p&gt;
&lt;p&gt;再度用存檔來比喻，大概就像你要讀檔的時候發現只有這兩個存檔：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新手村對話１&lt;/li&gt;
&lt;li&gt;魔王城決戰&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這下完蛋，如果什麼關鍵道具還是劇情沒有拿掉，要嘛放棄，要嘛認命從頭開始。所以，我們在 Commit 的時候要盡量迴避這個狀況。對此，我的建議是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在所有你覺得「這是一個段落」的時候就 Commit。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;就像這小節的開頭：完成了一個小功能？ Commit；重構了一個變數的命名？ Commit。在你所有想要 Commit 的時候 Commit，畢竟 Commit 不用錢，真的不用省。&lt;/p&gt;
&lt;p&gt;寧可多 Commit 幾次，等熟練 Git 的時候，&lt;s&gt;或是被靠夭洗版的時候&lt;/s&gt;，再考慮用 rebase 之類的技能來把多個零碎的 Commit 整理成一個；也不要臨時出了什麼事，結果 Git 一打開，只能回到一個月前，那真的是欲哭無淚。阿彌陀佛，保護自己，就從 Commit 開始。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你現在已經有整理 Commit 的需求，可以參照以下幾篇：&lt;br/&gt;
。 &lt;a href=&#34;https://gitbook.tw/chapters/using-git/amend-commit1.html&#34;&gt;【狀況題】修改 Commit 紀錄 - 為你自己學 Git &lt;/a&gt;&lt;br/&gt;
。 &lt;a href=&#34;https://medium.com/starbugs/use-git-interactive-rebase-to-organize-commits-85e692b46dd&#34;&gt;送 PR 前，使用 Git rebase 來整理你的 commit 吧！ - 星巴哥技術專欄&lt;/a&gt;&lt;br/&gt;
。 &lt;a href=&#34;https://gitbook.tw/chapters/rewrite-history/merge-multiple-commits-to-one-commit.html&#34;&gt;把多個 Commit 合併成一個 Commit - 為你自己學 Git&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&#34;git-commit-和-add-的-combo-技&#34;&gt;Git Commit 和 Add 的 Combo 技&lt;/h3&gt;
&lt;p&gt;讓我們延續一下 Add 章節的「懶還要更懶」，現在當我們完成一個變更，就要先 Add 到暫存區，再 Commit 到儲存庫。如果覺得這個兩步驟驗證很麻煩的話要怎麼辦呢？&lt;/p&gt;
&lt;p&gt;其實可以使用 Combo 技，一次搞定 Add 和 Commit，那就是 &lt;code&gt;-a&lt;/code&gt;，例如&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -a -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Update A.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但要注意這個招式只對已經在版本控制內的檔案有效，如果是新來的也還是要先去報到呦。&lt;/p&gt;
&lt;p&gt;關於這段 &lt;code&gt;工作區 -Add→ 暫存區 -Commit→ 儲存庫&lt;/code&gt; 的說明，也可以參見這篇為你自己學 Git 的&lt;a href=&#34;https://gitbook.tw/chapters/using-git/working-staging-and-repository.html&#34;&gt;工作區、暫存區與儲存庫&lt;/a&gt;，裡面用倉庫和廣場的比喻個人覺得很貼切。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;加入-gitignore-來忽略指定檔案&#34;&gt;加入 .gitignore 來忽略指定檔案&lt;/h3&gt;
&lt;p&gt;如果你跟我一樣，總是 &lt;code&gt;add -a&lt;/code&gt; 無差別加入，或是 GUI 的一鍵 Commit 用太爽，很容易就會翻車。怎麼個翻車法呢？我在新訓的時候就有被問過：&lt;/p&gt;
&lt;p&gt;「你把這推上來幹嘛？？？」&lt;/p&gt;
&lt;p&gt;沒錯！有些東西我們是不需要加到版本控制中的，例如每次編譯都會產生的檔案（像是 .net 的 bin 資料夾之類的）、機密檔案、個人對編譯器的設定檔等等。&lt;/p&gt;
&lt;p&gt;有些東西加入版本控制後，輕則讓你的 Git status 變得很雜亂、Commit 變得亂七八糟，重則影響到其他人的環境。因此我們要想辦法，讓這些東西不要加入版本控制裡。&lt;/p&gt;
&lt;p&gt;這時候我們就可以加入 Git Igonre 來讓 Git 忽略這些東西。&lt;/p&gt;
&lt;p&gt;現在讓我們來試試看，先新增一個 &lt;code&gt;Ignoreme.txt&lt;/code&gt; 檔案，接著再新增一個 &lt;code&gt;.gitignore&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4QWKh8Y.webp&#34;
  alt=&#34;&#34;width=&#34;281&#34; height=&#34;173&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後在 &lt;code&gt;.gitignore&lt;/code&gt; 中加上：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ignoreme.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接著，讓我們再次呼叫　&lt;code&gt;git status&lt;/code&gt; 確認當前的狀態&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/k0sNgcN.webp&#34;
  alt=&#34;&#34;width=&#34;928&#34; height=&#34;372&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以發現居然只有剛剛新增的 &lt;code&gt;.gitignore&lt;/code&gt;，Git 真的就裝作沒看到 &lt;code&gt;Ignoreme.txt&lt;/code&gt; 了！&lt;/p&gt;
&lt;p&gt;藉由 .gitignore 我們就能讓 Git 知道哪些檔案它不要亂插手，通常來說每個專案底下都會有一個共用的 .gitignore 檔案，避免某些人沒跟到就把不該推的東西給推上去了。&lt;/p&gt;
&lt;p&gt;至於哪些東西不該推呢，東西這麼多怎麼列得完呢？感謝社群，&lt;a href=&#34;https://github.com/github/gitignore&#34;&gt;github/gitignore&lt;/a&gt; 這兒都已經整理好了。就算裡面找不到的，只要 Google &amp;ldquo;你的語言或框架 + gitignore&amp;rdquo; 通常都會有結果，例如 &lt;a href=&#34;https://github.com/Arasz/dotnet-ignore&#34;&gt;dotnet-ignore&lt;/a&gt;，我們菜雞只要爽爽用，&lt;/p&gt;
&lt;p&gt;最後別忘了讓我們也把 &lt;code&gt;.gitigonre&lt;/code&gt; 也 Add 到我們的 Git 之中，並且 Commit 起來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WTr6t7f.webp&#34;
  alt=&#34;&#34;width=&#34;747&#34; height=&#34;466&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;查詢-commit-紀錄git-log&#34;&gt;查詢 Commit 紀錄（Git Log）&lt;/h2&gt;
&lt;p&gt;現在我們已經可以成功存檔了，但有玩過遊戲的都知道，通常都會有個地方讓你看當前的存檔和紀錄等等。那麼&lt;strong&gt;在 Git 中要怎麼看我們的 Commit 記錄呢&lt;/strong&gt;？這時候就要用到 &lt;code&gt;log&lt;/code&gt; 指令。&lt;/p&gt;
&lt;p&gt;由於只有存過一次也太寒酸了，現在讓我們把 A.txt 打開，將原本的 Hello! 改成 Hello world! 並且再 Commit 一次：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/uZY0Kjm.webp&#34;
  alt=&#34;&#34;width=&#34;160&#34; height=&#34;75&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IhLiS0z.webp&#34;
  alt=&#34;&#34;width=&#34;686&#34; height=&#34;198&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後，讓我們試試 &lt;code&gt;git log&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hD1c1h1.webp&#34;
  alt=&#34;&#34;width=&#34;757&#34; height=&#34;567&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;就可以看到我們前面的 Commit 內容囉！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：如果 Log 太多頁的話，可以用空白鍵前往下一頁、按 q 離開。 Git 的操作使用的是 Less，關於常用的操作方式可以參考 &lt;a href=&#34;https://en.wikipedia.org/wiki/Less_(Unix)#Frequently_used_commands&#34;&gt;Less (Unix) #Frequently used commands&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;每次的 Commit 資訊通常會包含&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作者&lt;/li&gt;
&lt;li&gt;日期&lt;/li&gt;
&lt;li&gt;Commit Messaage&lt;/li&gt;
&lt;li&gt;Commit 用 SHA-1 計算出來的識別碼，可以當成是這個 Commit 的唯一 ID、身分證編號就行了&lt;/li&gt;
&lt;li&gt;分支所在的 Commit
&lt;ul&gt;
&lt;li&gt;HEAD 是指向我們當前所在的分支，而我們 HEAD 所在的 master 分支則是在 743d&amp;hellip; 這個 Commit 上&lt;/li&gt;
&lt;li&gt;關於 HEAD 我們會在 &lt;a href=&#34;#git-%E7%9A%84-head-%E6%98%AF%E4%BB%80%E9%BA%BC&#34;&gt;Git 的 head 是什麼&lt;/a&gt; 繼續說明&lt;/li&gt;
&lt;li&gt;關於 分支 我們會在 &lt;a href=&#34;#git-%E7%9A%84%E5%88%86%E6%94%AFbranch%E6%98%AF%E4%BB%80%E9%BA%BC&#34;&gt;Git 的分支（branch）是什麼&lt;/a&gt; 繼續說明&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;題外話：如果有朋友在 windows 上，例如 powershell 使用 &lt;code&gt;git log&lt;/code&gt; 或 git 相關指令有出現&lt;strong&gt;中文亂碼&lt;/strong&gt;的情形，可以參考以下的語法進行調整：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git config --global core.quotepath false         # 引用路徑的檔案名稱
git config --global gui.encoding utf-8           # GUI 編碼
git config --global i18n.commit.encoding utf-8   # Commit 編碼
git config --global i18n.logoutputencoding utf-8 # Log 編碼
$env:LESSCHARSET=&amp;#39;utf-8&amp;#39;                         # Less 分頁的環境變數
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中環境變數需要直接前往 &lt;code&gt;系統內容 &amp;gt; 進階 &amp;gt; 環境變數&lt;/code&gt; 並新增到系統變數上，否則每次重開 powershell 都要重打一次。&lt;/p&gt;
&lt;p&gt;主要是因為一些歷史原因，Windows 在這塊並沒有全面支持 Utf-8，因此需要告訴 Git 我們要以 utf-8 作為編碼，並且也把 Git 用到的 Less 分頁等環境變數設定好。感謝 CSDN 的這篇 &lt;a href=&#34;https://blog.csdn.net/FollowGodSteps/article/details/96271359&#34;&gt;PowerShell | git log 中文亂碼問題解決&lt;/a&gt; 和 Github 上的 &lt;a href=&#34;https://gist.github.com/nightire/5069597&#34;&gt;Nightire 大大&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;但是如果你是 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/windows/wsl/install-win10&#34;&gt;在 Windows 也要安裝 Ubuntu，我就是要 Bashhhhh&lt;/a&gt; 的朋友就當我沒說。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;不過比起這樣的文字，我們比較常用的還是 Git 那相當有特色的線圖。現在讓我們試試&lt;br/&gt; &lt;code&gt;git log  --oneline --graph&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/V9ysh2L.webp&#34;
  alt=&#34;&#34;width=&#34;598&#34; height=&#34;218&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到 Log 變得相當精簡了。在我們後續會講到的&lt;a href=&#34;#git-%E7%9A%84%E5%88%86%E6%94%AFbranch%E6%98%AF%E4%BB%80%E9%BA%BC&#34;&gt;分支&lt;/a&gt;情景下，線圖還會幫忙畫出不同分支路線（平常有使用 Git 的 GUI 工具的朋友們應該都很熟悉了）&lt;/p&gt;
&lt;p&gt;這邊就借朋友的 Project 示範一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lHYIpqd.webp&#34;
  alt=&#34;&#34;width=&#34;889&#34; height=&#34;435&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了直接看線圖以外，&lt;code&gt;git log&lt;/code&gt; 也提供了相當多的參數可以運用，例如說可以用 &lt;code&gt;--committer&lt;/code&gt; 來找某個人的 Commit、用 &lt;code&gt;-S&lt;/code&gt; 尋找主旨等等、用 &lt;code&gt;-2&lt;/code&gt; 來限制只看兩筆等等，其他像是搜尋某個時間區段、某個特定檔案的變更紀錄也可以做到，甚至可以用 &lt;code&gt;--pretty&lt;/code&gt; 來自訂 log 要顯示的格式，基本上該有的功能都有。&lt;/p&gt;
&lt;p&gt;通常 &lt;code&gt;git log&lt;/code&gt; 最常見的場景大概就像這樣：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「Ｘ！這鬼東西誰寫的？？？」&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git log ShitCodeController.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;「噢，是我啊…」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這部份有興趣的朋友可以參考以下兩篇的說明，尤其是 為你自己學 Git 這篇裡面的狀況題相當實用，每天抓戰犯（？）的時候都會用到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/using-git/log.html&#34;&gt;檢視紀錄 - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://jamestw.logdown.com/posts/238719-advanced-git-log&#34;&gt;Git log 進階應用 - Jame&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;掌握 &lt;code&gt;git log&lt;/code&gt;，每天都可以更刺激有趣！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9BYAkeV.webp&#34;
  alt=&#34;&#34;width=&#34;500&#34; height=&#34;500&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;查詢兩個-commit-之間的差異git-diff&#34;&gt;查詢兩個 Commit 之間的差異（Git Diff）&lt;/h2&gt;
&lt;p&gt;有些朋友可能會問：「我知道每一次 Commit 了，但我還是不知道每個 Commit 到底動了哪些地方呀？」&lt;/p&gt;
&lt;p&gt;或是更進一步的：「我知道可以用 &lt;code&gt;git log -p&lt;/code&gt; 來看每次 Commit 的內容啦，也知道變動了哪些地方應該要直接能從 Commit Message 大致看出來啦。但 log 的雜訊太多了，我只想要確切知道兩個 Commit 間到底變動了哪些，該怎麼做？」&lt;/p&gt;
&lt;p&gt;這時候我們就可以&lt;strong&gt;使用 &lt;code&gt;git diff&lt;/code&gt; 來看兩個 Commit 之間的差異&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;現在讓我們回到 &lt;code&gt;git log&lt;/code&gt; 示範時的狀況：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hD1c1h1.webp&#34;
  alt=&#34;&#34;width=&#34;757&#34; height=&#34;567&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到我們有三個 Commit，分別是 a657, 6fbe, 2b33&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小提示：在告訴 git 某個 Commit 的 SHA1 值的時候，並不需要完整打完，只要到可區分的程度就可以了，畢竟這東西真的超級難重複嘛。如果 Git 分不出來，就會請你再打一次，不用擔心。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著讓我們輸入 &lt;code&gt;git diff a657 6fbe&lt;/code&gt; （請根據你的 Commit SHA1 值輸入）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HLjl7aj.webp&#34;
  alt=&#34;&#34;width=&#34;605&#34; height=&#34;355&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;就可以看到這兩個 Commit 間差異的內容囉！像是從上面的圖中，我們就可以知道 A.txt 有被修改過，其中被移除了 Hello 這一行，同時新增了 Hello World! 這一行。&lt;/p&gt;
&lt;p&gt;除了兩個 Commit 之間以外，&lt;code&gt;git diff&lt;/code&gt; 也可以查看兩個分支間的差異。&lt;/p&gt;
&lt;p&gt;我們前面有提到過每次 Commit 的流程是 工作區＞暫存區＞儲存庫，那如果我們現在要看工作區和儲存庫之間的差異，也就是「我們現在變更了什麼」該怎麼做呢？&lt;/p&gt;
&lt;p&gt;現在讓我們把 A.txt 的 Hello World! 的驚嘆號改成句號（可能我們不想那麼激動，想冷靜一點），但先不要 Commit：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8WlJXkE.webp&#34;
  alt=&#34;&#34;width=&#34;261&#34; height=&#34;106&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們用 &lt;code&gt;git status&lt;/code&gt; 確認一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zOf9MDZ.webp&#34;
  alt=&#34;&#34;width=&#34;764&#34; height=&#34;345&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到工作區已經有變更了，但我們尚未 &lt;code&gt;add&lt;/code&gt; 到暫存區裡。&lt;/p&gt;
&lt;p&gt;現在讓我們&lt;strong&gt;使用 &lt;code&gt;git diff&lt;/code&gt; 指令來看工作區和儲存庫間的差異&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/T2ajGae.webp&#34;
  alt=&#34;&#34;width=&#34;693&#34; height=&#34;369&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;能看到 Hello World! 和 Hello World. 的差異有確實出現。平時就可以用 &lt;code&gt;git diff&lt;/code&gt; 來看這次到底都改了些什麼。&lt;/p&gt;
&lt;p&gt;現在我們試試看先用 &lt;code&gt;git -add A.txt&lt;/code&gt;，把 A.txt 加入到
暫存中，再呼叫 &lt;code&gt;git diff&lt;/code&gt; 試試：



&lt;img
  src=&#34;https://image.igouist.net/g97cuY9.webp&#34;
  alt=&#34;&#34;width=&#34;620&#34; height=&#34;204&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;卻發現沒有找到任何差異了！這是因為 A.txt 已經被收入到暫存區中，不在工作區了。如果我們要&lt;strong&gt;確認暫存區和儲存庫之間的差異，要加上 &lt;code&gt;--cached&lt;/code&gt; 參數&lt;/strong&gt;，現在再用 &lt;code&gt;git diff --cached&lt;/code&gt; 試試：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/POr2E1J.webp&#34;
  alt=&#34;&#34;width=&#34;637&#34; height=&#34;367&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;就可以看到暫存區和儲存庫之間的差異囉。&lt;/p&gt;
&lt;p&gt;最後，示範完了就養成好習慣，順便 Commit 來結束這一小節吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UdDzOOZ.webp&#34;
  alt=&#34;&#34;width=&#34;711&#34; height=&#34;323&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git diff&lt;/code&gt; 的使用場景相當簡單，最常用來確認版本間的差異，或是檢查當前變更的內容。雖然 Diff 常常跑出來落落長一大串，不過現在的 GUI 都做得相當一目了然了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/dpdLhZo.webp&#34;
  alt=&#34;&#34;width=&#34;867&#34; height=&#34;244&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果手癢要自製 diff 介面的朋友也可以參考黑大的這篇 &lt;a href=&#34;https://blog.darkthread.net/blog/diff2html-webpage/&#34;&gt;Git 筆記 - 產生程式異動對照表(Compare List)&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另外，如果一次 Commit 包含的檔案太多，也可以像 &lt;code&gt;got log&lt;/code&gt; 一樣加上檔名來比較單一檔案的差異，或是使用 &lt;code&gt;--stat&lt;/code&gt; 來檢視簡單的變動檔案列表等等。&lt;/p&gt;
&lt;p&gt;diff 相關的文章也可以參照這幾篇，也是本節的主要參考資料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.1ju.org/git/git-diff&#34;&gt;git diff 命令&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10135441&#34;&gt;30 天精通 Git 版本控管 (09)：比對檔案與版本差異&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://awdr74100.github.io/2020-04-27-git-diff/&#34;&gt;Git 版本控制系統 - 比對檔案版本差異與標示說明&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是每次 &lt;code&gt;diff&lt;/code&gt; 的時候，我都還要查出兩個版本的 SHA1 碼，如果我只是要看這一版和前一版的差異，這麼簡單的場景卻要弄得那麼複雜，不是很麻煩嗎？這時候我們就可以借助 &lt;code&gt;HEAD&lt;/code&gt; 的力量，讓語法變得更簡單！&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;git-的-head-是什麼&#34;&gt;Git 的 HEAD 是什麼？&lt;/h2&gt;
&lt;p&gt;從上面的許多操作中，例如 &lt;a href=&#34;#%E6%9F%A5%E8%A9%A2-commit-%E7%B4%80%E9%8C%84git-log&#34;&gt;顯示 Commit Log 時&lt;/a&gt;，都可以看到 HEAD 這個關鍵字，例如 (HEAD -&amp;gt; master)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HEAD 基本上可以當作「目前位置」的概念，它是一個會指向當前分支的指標。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通常來說，HEAD 會指向目前所在的分支最新的一個 Commit（除非發生&lt;a href=&#34;#%E8%AA%8D%E8%AD%98-git-%E7%9A%84%E6%96%B7%E9%A0%ADdetached-head&#34;&gt;斷頭&lt;/a&gt; ）。&lt;/p&gt;
&lt;p&gt;但由於我們還沒有認識分支，當然也沒有針對分支進行任何操作，因此 HEAD 就會指向我們的預設分支 master（Github 現在改叫做 main 了），所以現在直接把 HEAD 當成目前所在位置就可以了。&lt;/p&gt;
&lt;p&gt;同時，也可以用 HEAD 搭配 &lt;code&gt;^&lt;/code&gt;（上一個版本）、&lt;code&gt;~&lt;/code&gt;（上幾個版本）來直接替代大多數需要給定 Commit SHA1 值的操作，例如說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HEAD~5&lt;/code&gt; = 目前位置往前五步&lt;/li&gt;
&lt;li&gt;&lt;code&gt;f74d^&lt;/code&gt; = f74d 這個 Commit 的上一個 Commit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這樣會讓整個 Git 指令操作變得相當簡便，當我們在 &lt;code&gt;git diff&lt;/code&gt; 的時候，明明只是要看跟上一版或者前面幾版的差異，實在很不想再查 SHA1 碼，就可以用 &lt;code&gt;git diff HEAD HEAD^&lt;/code&gt; 或是 &lt;code&gt;git diff HEAD HEAD~2&lt;/code&gt; 的指令來加快查詢。&lt;/p&gt;
&lt;p&gt;關於 HEAD 的延伸閱讀，可以參考這兩篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/using-git/what-is-head.html&#34;&gt;【冷知識】HEAD 是什麼東西？ - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://titangene.github.io/article/git-head-ref.html&#34;&gt;深入 Git：HEAD refs - Titangene Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;git-revert讓我們往回走一步&#34;&gt;Git Revert：讓我們往回走一步&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;「以前我沒得選，現在我想做個好人。」&lt;/p&gt;
&lt;p&gt;『好啊，去跟 Git 說，看他讓不讓你做好人』&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;既然我們已經學會存檔，也能看過去都存了哪些檔了，是時候該學學讀檔了吧！&lt;/p&gt;
&lt;p&gt;大多數的遊戲都有「上一步」、「悔棋」、「恢復上一動！懷疑啊？」這類的動作，在 Git 中則是叫做 &lt;code&gt;Revert&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;不過比起「我這一步完全不算」，&lt;strong&gt;&lt;code&gt;Revert&lt;/code&gt; 比較像是「我再把棋子移回去就是了」的概念&lt;/strong&gt;。也就是說，雖然是悔棋，但是這個悔棋本身也算是一步的意思。&lt;/p&gt;
&lt;p&gt;這個部份直接試看看會比較好了解，讓我們把 A.txt 打開，並且把內容改成&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Hello world. HAHAHA.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;緊接著直接 &lt;code&gt;Commit -a -m &amp;quot;HAHA&amp;quot;&lt;/code&gt;，現在的 Git Log 應該會長得像這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WWGzQQh.webp&#34;
  alt=&#34;&#34;width=&#34;592&#34; height=&#34;294&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後可能我們去吃個飯，越想越不對勁，回來決定不要這次 Commit 了。&lt;/p&gt;
&lt;p&gt;現在讓我們試試 Revert：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git revert HEAD  --no-edit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/P7kJY5C.webp&#34;
  alt=&#34;&#34;width=&#34;513&#34; height=&#34;255&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著再確認一次 Log：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lqLNNYX.webp&#34;
  alt=&#34;&#34;width=&#34;591&#34; height=&#34;297&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到又多了一個 Commit，這個 Commit 用來撤回前一個 Commit。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PhPCFqm.webp&#34;
  alt=&#34;&#34;width=&#34;348&#34; height=&#34;105&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且 A.txt 也變回沒有 HAHA 的版本囉。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：我們在前面 Revert 的時候，加上了 &amp;ndash;no-edit 的參數，讓 Git 自動使用預設的 Revert 訊息去 Commit。&lt;/p&gt;
&lt;p&gt;大多時候，Revert &amp;ldquo;XXX&amp;rdquo; 就很清楚表達要撤回這個 Commit 的意思了。但如果這次撤回有需要另外說明的部分，可以不加上 &amp;ndash;no-edit 參數，會進入 Commit Message 的編輯頁面，包含這個 Commit 的變動內容，讓你輸入這次 Revert 的 Commit Message。輸入完之後就可以提交囉。&lt;/p&gt;
&lt;p&gt;如果你是誤入 Vim 的朋友，呃，保佑你能順利 :wq。跟我一樣認命看為你自己學 Git 的 &lt;a href=&#34;https://gitbook.tw/chapters/command-line/vim-introduction.html&#34;&gt;超簡明 Vim 操作介紹&lt;/a&gt; 吧，畢竟這是大多數人的必踩之坑…&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;如果突然又不想撤回了怎麼辦？你可以再 Revert 一次來 Revert 掉上個 Revert 藉此還原上個
Revert 所 Revert 掉的 Commit，夠直覺吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「我識破你的識破！」—— 某風聲（桌遊）玩家&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;那如果並不是要 &lt;code&gt;Revert&lt;/code&gt; 這種後悔一步的作法，不想要背負 Revert 的 Commit，而是想要重新出發，該怎麼辦呢？&lt;/p&gt;
&lt;p&gt;這時候就要用 &lt;code&gt;Reset&lt;/code&gt; 了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;git-reset讓我們搭上時光機&#34;&gt;Git Reset：讓我們搭上時光機&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;「要是能重來，我要選李白。幾百年前寫的 Bug 沒那麼多人幹」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Reset&lt;/code&gt; 的意思是 &lt;strong&gt;把當前的位置 &lt;code&gt;Reset&lt;/code&gt; 到新的位置&lt;/strong&gt;，就像時光機一樣，直接前往目標 Commit。&lt;/p&gt;
&lt;p&gt;用起來有點像 Git 界的 &lt;code&gt;goto&lt;/code&gt; 的感覺，現在就讓我們直接試試吧。&lt;/p&gt;
&lt;p&gt;首先掌握一下狀況，現在我們的 Log 是這個樣子：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lqLNNYX.webp&#34;
  alt=&#34;&#34;width=&#34;591&#34; height=&#34;297&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後 A.txt 裡面則是：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PhPCFqm.webp&#34;
  alt=&#34;&#34;width=&#34;348&#34; height=&#34;105&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在假設我們想要取消前面對 A.txt 中 Hello 的所有操作，也就是回到還沒有做 &lt;code&gt;a657 fix: 修正 hello 為 hello world&lt;/code&gt; 這個 Commit 之前。&lt;/p&gt;
&lt;p&gt;我們可以從 &lt;code&gt;git log&lt;/code&gt; 中看見還沒做 &lt;code&gt;a657&lt;/code&gt; 的前一個 Commit 是 &lt;code&gt;6fbe Add gitignore&lt;/code&gt;，或是我們也可以直接用 &lt;code&gt;a657^&lt;/code&gt; 來直接指定 &lt;code&gt;a657&lt;/code&gt; 的前一個 Commit。先讓我們用 &lt;code&gt;diff&lt;/code&gt; 偷看一下目的地和當前的差異。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ltpNAm7.webp&#34;
  alt=&#34;&#34;width=&#34;605&#34; height=&#34;380&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到 A.txt 的在當時的內容是 &lt;code&gt;Hello!&lt;/code&gt;，這就是我們要的。這時候我們就可以使用 &lt;code&gt;reset&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git reset --hard a657^
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TdP0vZf.webp&#34;
  alt=&#34;&#34;width=&#34;552&#34; height=&#34;185&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;Git 跟我們說「你已經回到了 &lt;code&gt;6fbe&lt;/code&gt; 這個 Commit 囉」接著讓我們確認看看 A.txt：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ijZA2K2.webp&#34;
  alt=&#34;&#34;width=&#34;351&#34; height=&#34;105&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;的確也變回去了，大功告成。&lt;/p&gt;
&lt;p&gt;眼尖的朋友應該已經發現了，我們在 &lt;code&gt;reset&lt;/code&gt; 的時候，加上了 &lt;code&gt;--hard&lt;/code&gt; 的參數，這是什麼意思呢？其實 &lt;code&gt;reset&lt;/code&gt; 有分為三種模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mixed：保留工作區，不保留暫存區&lt;/strong&gt;。沒有選模式的時候預設就是用 Mixed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Soft：保留工作區，也保留暫存區&lt;/strong&gt;。感覺會像是只是把 HEAD 往前移而已&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard：不保留工作區，也不保留索引區&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;也就是所有變更都直接捨棄，完整地回到當時&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用現在這個例子來說，我們知道 A.txt 的舊版會是 Hello!，新版會是 Hello world.，那麼這三個模式下就會變成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Mixed&lt;/code&gt;：A.txt 會是 &lt;code&gt;Hello world.&lt;/code&gt; 但還沒有 Add 的狀態&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Soft&lt;/code&gt;：A.txt 會是 &lt;code&gt;Hello world.&lt;/code&gt; 並且已經 Add 等待 Commit 的狀態&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Hard&lt;/code&gt;：A.txt 會是 &lt;code&gt;Hello!&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這三個模式可以用在不同的場景，例如說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;當我只是想要重新編輯 Commit Message，或是想要整理某些部分的 Code，就可以用 &lt;code&gt;Mixed&lt;/code&gt; 或 &lt;code&gt;Soft&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;當我發現一些東西不應該 Add 進去 Git 版控，就可以用 &lt;code&gt;Mixed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;當我打算把這整組 Commit 放生，什麼都不要了，只想著回到當時狀況，就可以用 &lt;code&gt;hard&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;等等這種情景，再自己取捨一下要用哪種時空旅行方法囉。&lt;/p&gt;
&lt;p&gt;關於 &lt;code&gt;reset&lt;/code&gt; 和這三個模式，為你自己學 Git 的表格整理得很不錯。這邊加上延伸閱讀（其實就是為你自己學 Git 的 Revert, Reset 系列文）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/using-git/reset-commit.html&#34;&gt;【狀況題】剛才的 Commit 後悔了，想要拆掉重做 - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/using-git/restore-hard-reset-commit.html&#34;&gt;【狀況題】不小心使用 hard 模式 Reset 了某個 Commit，救得回來嗎？ - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/rewrite-history/reset-revert-and-rebase.html&#34;&gt;Reset、Revert 跟 Rebase 指令有什麼差別？ - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;題外話：如果真的遇到要把目前的 Commit 整條捨棄回到某個時間點的狀況，建議還是用後面學到的分支，把不要的 Commit 先拉出一條分支留著一陣子。&lt;/p&gt;
&lt;p&gt;經歷過一些已經宣告死刑的需求過陣子借屍還魂死灰復燃的神奇操作之後，真的會感謝 Git 救工程師於水火之中。阿彌陀佛。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：在 &lt;code&gt;reset&lt;/code&gt; 的時候真的會強烈感覺到 Commit 的頻率和 Commit Message 的重要，想想前面 Commit 章節的那兩個例子：&lt;/p&gt;
&lt;p&gt;如果只有「專案初始化」、「功能完成」兩個 Commit，那 就算 &lt;code&gt;reset&lt;/code&gt; 也無用武之地，如果 Commit 訊息一整排都是「Add」、「Add」、「Add」，那給你 &lt;code&gt;reset&lt;/code&gt; 也不知道該往哪裡去。&lt;/p&gt;
&lt;p&gt;所以切記切記，魔鬼藏在 Commit 裡！&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;git-的分支branch是什麼&#34;&gt;Git 的分支（Branch）是什麼？&lt;/h2&gt;
&lt;p&gt;就算是超級英雄片也會有團隊合作各司其職的時候，程式開發上一定也會遇到多人合作的狀況，這時候我們就不能一路無腦 Commit 下去。&lt;/p&gt;
&lt;p&gt;像是分組作業的時候，通常都會溝通好每個人負責的地方，最後再銜接起來；又或是要前往目的地的時候，可以大家各自約好用不同的方法前往，在目的地會合。在 Git 中，我們可以使用分支（Branch）的方式來達到一樣的效果。例如說，多個人的時候就會變成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小明 拉了Ａ分支 負責開發甲功能&lt;/li&gt;
&lt;li&gt;小華 拉了Ｂ分支 負責修復出問題的乙功能&lt;/li&gt;
&lt;li&gt;小美 拉了Ｃ分支 負責開發另一個專案的丙功能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後再進行合併。&lt;/p&gt;
&lt;p&gt;就算是只有一個人開發，有時候也會遇到正在開發某一個功能的時候，發現其他功能的某個地方壞了，又不想混在一起做。這時候也可以用分支進行管理。&lt;/p&gt;
&lt;p&gt;這個部份我推薦連猴子都能懂的 Git 指南中的 &lt;a href=&#34;https://backlog.com/git-tutorial/tw/stepup/stepup1_1.html&#34;&gt;什麼是分支？&lt;/a&gt; 這頁的&lt;a href=&#34;https://backlog.com/git-tutorial/tw/img/post/stepup/capture_stepup1_1_2.png&#34;&gt;圖例&lt;/a&gt;，可以看到各自開發不同功能之後，合併在一起取得有全部變更的網頁。&lt;/p&gt;
&lt;p&gt;分支在大多範例中會被形容像是樹枝的分枝，從主幹發散出去。但我個人覺得比較像是&lt;strong&gt;水流&lt;/strong&gt;，從任何一處我們都可以岔分出另一條水流，這兩條水流各自經歷了不同的地方，可能第一條水流帶來了泥沙，第二條河流被汙染了核廢料，而我們又能將這兩條水流會合在一起，得到同時有泥沙和核廢料的水流。&lt;/p&gt;
&lt;p&gt;而個人認為，分支最重要的概念就是進行&lt;strong&gt;環境的隔離&lt;/strong&gt;，大家可以在自己的分支上進行作業，又不會互相影響。就算想修改看看，也可以在自己的分支上先行測試，沒問題了再合併上去，這樣就能在工作流程上貫徹封裝、降耦合和單一職責的精神。並且，也因為多條分支可以同時進行不同的工作，也能達到平行處理般的效率，重要的是戰犯更好找了，豈不妙哉。&lt;/p&gt;
&lt;p&gt;那麼，我們現在就來記錄一下分支的基本操作吧。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;新建分支branch與切換分支checkout&#34;&gt;新建分支（branch）與切換分支（checkout）&lt;/h3&gt;
&lt;p&gt;我們在預設狀況下，就會位於 Master（有些地方是 Main）分支上，首先讓我們從新建分支與切換分支開始嘗試。在 &lt;code&gt;hello-git&lt;/code&gt; 中，新增兩個檔案：&lt;code&gt;B.txt&lt;/code&gt; 和 &lt;code&gt;C.txt&lt;/code&gt;，現在資料夾應該會是這個樣子：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gEWbVkJ.webp&#34;
  alt=&#34;&#34;width=&#34;375&#34; height=&#34;266&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著將這兩個新檔案 Add 進來並且 Commit：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qXwq3WC.webp&#34;
  alt=&#34;&#34;width=&#34;609&#34; height=&#34;293&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到我們的 master 分支位於最新的 Commit 07672f4 上了，接著我們要從這裡&lt;br/&gt; &lt;strong&gt;用 &lt;code&gt;git branch&lt;/code&gt; 拉出一條新的分支&lt;/strong&gt;：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git branch Branch-B
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;補充：&lt;strong&gt;分支拉出來的 Commit 點稱為 &lt;code&gt;Base&lt;/code&gt;&lt;/strong&gt;。由於任兩個 Branch 一定會找得到一個共同的 base，因此我們在對分支間做合併和比較時，並不是將整份拿出來做大量核對，而是從這個 Base 當作基準點來進行變更的比較。這個 base 的概念和我們後續的 &lt;a href=&#34;#git-merge%E8%AE%93%E6%88%91%E5%80%91%E5%90%88%E4%BD%B5%E5%85%A9%E6%A2%9D%E5%88%86%E6%94%AF%E5%90%A7&#34;&gt;合併&lt;/a&gt;、&lt;a href=&#34;#%E4%BB%80%E9%BA%BC%E6%98%AF%E8%A1%9D%E7%AA%81conflict&#34;&gt;衝突&lt;/a&gt; 等操作會比較相關。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果建立分支的時候不小心取錯名字之類的，可以先偷看後面的 &lt;a href=&#34;#%E6%88%91%E5%80%91%E7%8F%BE%E5%9C%A8%E6%9C%89%E5%93%AA%E4%BA%9B%E5%88%86%E6%94%AFgit-branch%E8%A6%81%E6%80%8E%E9%BA%BC%E5%88%AA%E9%99%A4%E5%88%86%E6%94%AF&#34;&gt;查詢分支列表、刪除分支&lt;/a&gt; 的小節。如果一切順利的話，我們後續在 &lt;del&gt;利用完分支&lt;/del&gt; 跑完整個流程才會進行分支的刪除，請繼續看下去囉～&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;建立分支後，我們再&lt;strong&gt;使用 &lt;code&gt;git checkout&lt;/code&gt; 簽出分支&lt;/strong&gt;：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git checkout Branch-B
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;應該可以看到 Git 告訴你已經切換到 Branch-B 了，我們也可以用 &lt;code&gt;git status&lt;/code&gt; 來進行確認：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mrektkG.webp&#34;
  alt=&#34;&#34;width=&#34;543&#34; height=&#34;312&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;題外話：用 &lt;code&gt;branch&lt;/code&gt; 來建立新分支應該相當直覺，但為什麼是用 &lt;code&gt;checkout&lt;/code&gt; 來切換分支呢？&lt;/p&gt;
&lt;p&gt;這是因為在 Git 的觀念中，比較像是「&lt;strong&gt;在圖書館的櫃台，我們去借出某一本指定的書，完事後再歸還回去&lt;/strong&gt;」這樣的概念，因此我們才會使用 &lt;code&gt;checkout&lt;/code&gt; 簽出的方式來取出我們要的分支。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;確認我們現在是在 Branch-B 分支之後，現在請把 B.txt 刪除，並且 Commit：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2cxGS7c.webp&#34;
  alt=&#34;&#34;width=&#34;692&#34; height=&#34;269&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著，讓我們切換回 Master 分支：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git checkout master
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;觀察一下，B.txt 是不是復活了？&lt;/p&gt;
&lt;p&gt;現在我們可以再練習一次：新建一條 Branch-C 並簽出，並且刪除 C.txt 後 Commit。&lt;/p&gt;
&lt;p&gt;如果覺得每次都要先新建分支，再跳過去做兩步很麻煩的朋友，可以試試 &lt;code&gt;git checkout -b Branch-C&lt;/code&gt;，就可以在簽出的同時建立分支囉。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/o68UUxJ.webp&#34;
  alt=&#34;&#34;width=&#34;507&#34; height=&#34;186&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著移除 C.txt 之後 Commit：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/r5eNNrE.webp&#34;
  alt=&#34;&#34;width=&#34;584&#34; height=&#34;241&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在，可以試著用 &lt;code&gt;checkout&lt;/code&gt; 在 Branch-B 和 Branch-C 之間來回切換並觀察一下，就能稍微體會到分支之力囉。&lt;/p&gt;
&lt;p&gt;延伸閱讀：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/branch/branch-from-old-commit.html&#34;&gt;【狀況題】我可以從過去的某個 Commit 再長一個新的分支出來嗎？ - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&#34;git-stash讓我們快速存個檔&#34;&gt;Git Stash：讓我們快速存個檔&lt;/h3&gt;
&lt;p&gt;要注意，checkout 只能在已經 Commit 的情況下進行。如果萬不得已必須中斷手上工作切換到其他分支的時候（例如突然被叫去修正式環境的東西），可以使用 &lt;code&gt;stash&lt;/code&gt; 來做暫存的動作。&lt;/p&gt;
&lt;p&gt;關於 &lt;code&gt;stash&lt;/code&gt; 的使用方式，由於我個人比較少用，故不再贅述，可以參照以下兩篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kingofamani.gitbooks.io/git-teach/content/chapter_3_branch/stash.html&#34;&gt;Stash暫存 · GIT教學&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2019/11/git-stash.html&#34;&gt;菜鳥工程師 肉豬: Git stash 暫存正在修改的內容&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2021.04.30 補充:&lt;/p&gt;
&lt;p&gt;才剛說比較少用而已，結果沒多久就遇到需要使用 &lt;code&gt;stash&lt;/code&gt; 來暫存的時候囧。所以這邊還是簡單記一下語法，感謝 &lt;a href=&#34;https://backlog.com/git-tutorial/tw/reference/stash.html&#34;&gt;連猴子都能懂的 Git 入門指南&lt;/a&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git stash&lt;/code&gt; 直接進行暫存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git stash save&lt;/code&gt; 可以在 save 後面替這次暫存取名字&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git stash list&lt;/code&gt; 可以看現在有哪些暫存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git stash pop&lt;/code&gt;  可以取出最新的暫存，或指定 ID 來取出指定暫存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git stash drop&lt;/code&gt; 可以刪除最新的暫存，或指定 ID 來刪除指定暫存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git stash clear&lt;/code&gt; 刪除所有暫存&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&#34;git-merge讓我們合併兩條分支吧&#34;&gt;Git Merge：讓我們合併兩條分支吧&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;「真是太諷刺了紹安，你拉了新分支繞了一大圈，&lt;br/&gt;　最後做出來的 feature 竟然是你不想做的，你老闆的需求。&lt;br/&gt;
　所以說呢，分支最後終究是要回到 master 來的，&lt;br/&gt;　這四千個 Commit 的盡頭 Merge，或許正是你的極限也說不定。」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;不管是新功能的開發，問題的修復，效能的測試等等，&lt;strong&gt;大多數以上的分支最終都是需要合併回來的&lt;/strong&gt;。每個專案大概就像那種機器人合體戰隊的動畫的每一集一樣，最後都要來 Merge 一下，可以說是避無可避。&lt;/p&gt;
&lt;p&gt;我們在前面一節有開出了兩個分支：Branch-B 和 Branch-C，現在讓我們把他們合而為一。首先，讓我們先簽出到 Branch-B：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git checkout Branch-B
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接著&lt;strong&gt;使用 &lt;code&gt;git merge&lt;/code&gt; 指令來用 Branch-B 分支把 Branch-C 分支給吃進來&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9G1qj6g.webp&#34;
  alt=&#34;&#34;width=&#34;554&#34; height=&#34;280&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這時候可以觀察看看 &lt;code&gt;hello-git&lt;/code&gt; 資料夾，B.txt 和 C.txt 應該都已經消失了。&lt;/p&gt;
&lt;p&gt;我們可以用 &lt;code&gt;git log&lt;/code&gt; 來觀察一下：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XHzq9yk.webp&#34;
  alt=&#34;&#34;width=&#34;674&#34; height=&#34;295&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見，我們在 Branch-C 所做的 &lt;code&gt;Delete C.txt&lt;/code&gt; 這個 Commit 也出現在 Branch-B 的記錄中的，並且 C.txt 也確實消失了。&lt;/p&gt;
&lt;p&gt;這邊可以注意，當我們用 Branch-B 把 Branch-C 吃進來的時候，Branch-C 本身並沒有任何改變，仍然停留在 &lt;code&gt;Delete C.txt&lt;/code&gt; 這個 Commit 上，而用來吃掉對方的 Branch-B 則取得了所有 Branch-C 的變更。&lt;/p&gt;
&lt;p&gt;所以合併的時候要注意一下當前的分支，是&lt;strong&gt;將目標分支匯入到當前所在的分支&lt;/strong&gt;，不要弄反了，不然把一堆測試用的東西合併上去正式環境就完蛋啦。&lt;/p&gt;
&lt;p&gt;現在我們再把 Branch-B 的變更也合併進 master 吧。&lt;/p&gt;
&lt;p&gt;同樣地，先簽出 master，再 &lt;code&gt;git merge Branch-B&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pEySmri.webp&#34;
  alt=&#34;&#34;width=&#34;1014&#34; height=&#34;580&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就成功將兩個負責不同項目的分支裡的變更都合併回 master 囉！&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;什麼是衝突conflict&#34;&gt;什麼是衝突（Conflict）？&lt;/h3&gt;
&lt;p&gt;當然，不是每一次合併都能順利，就像不是每次合作都能順利一樣。只要兩個分支&lt;strong&gt;有共同增刪改同一份文件同一個區塊，在合併的時候就會發生衝突&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;衝突的時候我們得要開啟檔案逐一審查有衝突的區塊，並選擇使用對方還是自己還是手動合併成新的版本。&lt;/p&gt;
&lt;p&gt;現在讓我們來實際操作看看吧：&lt;/p&gt;
&lt;p&gt;首先，讓我們先確認自己在 master，然後新建並簽出一支新的分支 &lt;code&gt;Branch-X&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果要同時做簽出並且新建分支的話，可以在 &lt;code&gt;checkout&lt;/code&gt; 的時候加上 &lt;code&gt;-b&lt;/code&gt; 試試：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git checkout -b Branch-X
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然後，將 A.txt 的內容改成 &lt;code&gt;Hello X!&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WeFQiSM.webp&#34;
  alt=&#34;&#34;width=&#34;299&#34; height=&#34;104&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且 Commit：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git commit -a -m &amp;#34;Hello X!&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;緊接著，讓我們回到 master，重新建立並簽出另一支分支 &lt;code&gt;Branch-Y&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git checkout master
git checkout -b Branch-Y
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這次我們把 A.txt 的內容改成 &lt;code&gt;Hello Y!&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/us5hqzk.webp&#34;
  alt=&#34;&#34;width=&#34;336&#34; height=&#34;117&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且 Commit：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git commit -a -m &amp;#34;Hello Y!&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;現在，我們手頭上有 Branch-X 和 Branch-Y 兩條分支了，並且他們都變更過了 A.txt，現在我們直接用 Branch-Y 來合併 Branch-X：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git merge Branch-X
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4j9GJmA.webp&#34;
  alt=&#34;&#34;width=&#34;744&#34; height=&#34;236&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到 Git 跳出訊息跟你說&lt;strong&gt;自動合併失敗（Automatic merge failed）&lt;/strong&gt;，要求你進行修復。&lt;/p&gt;
&lt;p&gt;並且 &lt;code&gt;git status&lt;/code&gt; 也會告訴你，你有未處理的 merge 問題，A.txt 這個檔案都有被變更到：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/14o8dB9.webp&#34;
  alt=&#34;&#34;width=&#34;806&#34; height=&#34;421&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這時候讓我們去看看 A.txt 的內容：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/etAf0gj.webp&#34;
  alt=&#34;&#34;width=&#34;320&#34; height=&#34;186&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;會看到 Git 標示出發生衝突的部分，上半部是我們當前 HEAD 也就是 Branch-Y 的內容，下部分則是 Branch-X 的內容。&lt;/p&gt;
&lt;p&gt;我們&lt;strong&gt;必須把這個衝突的部分處理好，並且把這些標示拿掉，再重新 Commit 一次&lt;/strong&gt;。假設Ｘ跟Ｙ兩位同事經過 &lt;del&gt;扭打&lt;/del&gt; 討論之後，決定改成兩個人的名字都要標在上面，變成 &lt;code&gt;Hello X &amp;amp; Y!&lt;/code&gt;，我們就可以進行 commit 來結束衝突囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1HXkePJ.webp&#34;
  alt=&#34;&#34;width=&#34;795&#34; height=&#34;245&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;通常來說，如果是使用 GUI 工具的朋友，應該會像 &lt;code&gt;Diff&lt;/code&gt; 那樣給一個對照的列表讓你選擇，例如說我平常用的 Visual Studio 就會並排讓你勾選要使用哪一邊的，或是自己修改（這邊借用一下官方文件的圖）：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://learn.microsoft.com/zh-tw/visualstudio/version-control/media/vs-2022/git-conflicts-resolve-conflict.png?view=vs-2022&#34;
  alt=&#34;&#34;width=&#34;1434&#34; height=&#34;1158&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;又或是可以直接選擇「使用當前分支」、「使用對方分支」的這類方便選項。但要特別注意一點，就是衝突一定要好好解開再上傳，&lt;strong&gt;絕對不要沒解衝突就直接 Commit 掉推上來造成大家困擾&lt;/strong&gt;，這個一定要特別記得，弄不好可是會折壽的，物理上的折壽。&lt;/p&gt;
&lt;p&gt;通常來說，發生衝突的時候必須了解兩方修改的內容和意圖，並且協調好處理的方式，再編寫成程式碼並解除衝突。這時候我就很佩服幫忙合併的前輩，我真的會看到眼花。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果衝突的檔案不是文字檔而是圖片檔，又或者是接下來要說明的 rebase 造成的衝突，處理的方式會不太一樣。可以參考為你自己學 Git 的這篇 &lt;a href=&#34;https://gitbook.tw/chapters/branch/fix-conflict.html&#34;&gt;合併發生衝突了，怎麼辦？&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&#34;git-rebase讓我們移花接木&#34;&gt;Git Rebase：讓我們移花接木&lt;/h3&gt;
&lt;p&gt;在處理分支的時候還有另一種合併方式，就是使用 Rebase。&lt;/p&gt;
&lt;p&gt;我們在前面分支的章節有稍微提起過，分支的 base 是建立出分支的 Commit，也是分支和其他分支比較的基準點。&lt;strong&gt;而 rebase 就是「重新設定基準點」的意思&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;剛剛在 merge 的時候，我們已經將 Branch-X 併入了 Branch-Y ，現在就試試用 rebase 的方式，將 Branch-X 併入 master 吧。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git checkout master
git rebase Branch-Y
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這樣就會將 master 的 base 重新設定到 Branch-Y 分支上囉，因為我們的狀況比較簡單，所以比較像是 master 推進到了  Branch-Y 的進度，讓我們用 &lt;code&gt;git log&lt;/code&gt; 確認一下當前的狀況吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/sBPfJRR.webp&#34;
  alt=&#34;&#34;width=&#34;713&#34; height=&#34;555&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到剛剛在 Branch-Y 上的操作，例如解衝突的 Commit 都已經進到 master 來了。&lt;/p&gt;
&lt;p&gt;Rebase 跟 Merge 最明顯的差別在於 Rebase 是將這個分支 &lt;strong&gt;逐步移植&lt;/strong&gt; 到另一個分支上，而不是像 Merge 將兩條水流引流成一條，所以並不會有合併時的 Commit。&lt;/p&gt;
&lt;p&gt;但也由於是從切出分支的基準點開始做移植和計算的動作，我個人是覺得相對比較危險的，例如說你的 Git 分支樹就會和別人的那一份產生差異等等，畢竟這是一個變更歷史的行為，因此還是小心使用比較好，我個人是盡量能用 Merge 就用 Merge 的流派啦。&lt;/p&gt;
&lt;p&gt;關於 Rebase 的部分，為你自己學 Git 的這篇 &lt;a href=&#34;https://gitbook.tw/chapters/branch/merge-with-rebase.html&#34;&gt;另一種合併方式（使用 rebase）&lt;/a&gt; 相當詳細，有影片還有圖解及 rebase 的步驟說明，失敗救回的方法。如果有需要用到 rebase 來處理，可以先稍微閱讀一下。&lt;/p&gt;
&lt;p&gt;還有 Rebase 和 Merge 兩種合併方法的差異，可以參見猴子都會的 Git 的這篇 &lt;a href=&#34;https://backlog.com/git-tutorial/tw/stepup/stepup1_4.html&#34;&gt;分支的合併&lt;/a&gt;，有針對 Merge 和 Rebase 代表的操作進行圖解。&lt;/p&gt;
&lt;p&gt;此外，Rebase 也可以用來修改先前的 Commit 之類的，例如搭配 &lt;code&gt;squash&lt;/code&gt; 來把多個 Commit 壓縮成一個 Commit，請參見 &lt;a href=&#34;https://backlog.com/git-tutorial/tw/stepup/stepup7_6.html&#34;&gt;使用 rebase -i 合併提交&lt;/a&gt;、&lt;a href=&#34;https://medium.com/starbugs/use-git-interactive-rebase-to-organize-commits-85e692b46dd&#34;&gt;送 PR 前，使用 Git rebase 來整理你的 commit 吧！&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;我們現在有哪些分支git-branch要怎麼刪除分支&#34;&gt;我們現在有哪些分支（Git Branch）？要怎麼刪除分支？&lt;/h3&gt;
&lt;p&gt;現在我們針對分支的基本操作已經告一段落。可以用 &lt;code&gt;git branch&lt;/code&gt; 來稍微看一下本地端的儲存庫都開了哪些分支。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/QPcfALx.webp&#34;
  alt=&#34;&#34;width=&#34;542&#34; height=&#34;298&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;前面加上 * 號的就是當前所在的分支，如果已經和遠端儲存庫連線的朋友，也可以使用 &lt;code&gt;-r&lt;/code&gt; 參數來看遠端儲存庫上的分支，用 &lt;code&gt;-a&lt;/code&gt; 來看本地和遠端的所有分支。另外，遠端儲存庫的分支將會用 &lt;code&gt;remotes/&lt;/code&gt; 開頭。&lt;/p&gt;
&lt;p&gt;由於前面示範合併和衝突的分支們的工作都已經告一段落了，我們現在就要稍微清理一下它們。要刪除分支，只需要加上 &lt;code&gt;-d&lt;/code&gt; 參數就可以了，像現在這個例子就是：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git branch -d Branch-B
git branch -d Branch-C
git branch -d Branch-X
git branch -d Branch-Y
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CaYIfDt.webp&#34;
  alt=&#34;&#34;width=&#34;554&#34; height=&#34;401&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著再讓我們用 &lt;code&gt;git branch&lt;/code&gt; 確認一下吧：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2aslDOI.webp&#34;
  alt=&#34;&#34;width=&#34;536&#34; height=&#34;188&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;認識-git-的斷頭detached-head&#34;&gt;認識 Git 的斷頭（detached HEAD）&lt;/h3&gt;
&lt;p&gt;我們在前面切換分支的時候，主要是使用 &lt;code&gt;checkout&lt;/code&gt; 來進行。但如果我們並不是針對某個 branch 去做 &lt;code&gt;checkout&lt;/code&gt;，而是對某個 commit 去做的時候，就會變成斷頭狀態。&lt;/p&gt;
&lt;p&gt;因為 HEAD 會指向當前的分支，所以當我們用 checkout 並不是用來切換分支，而是單純回到某個 Commit 的時候，Git 實際上是幫我們建立一個未命名的分支，這時候如果又做了一些事情並 Commit ，又來回跳（例如說 &lt;code&gt;checkout&lt;/code&gt; 到別的地方去），我們就會找不到這個仍未取名的陌生分支，並遺失掉這些變更。&lt;/p&gt;
&lt;p&gt;這也就是為什麼上面的 checkout 範例，會有一大段警告，告訴你你的 HEAD 掉了，現在在斷頭狀態。&lt;/p&gt;
&lt;p&gt;這時候如果要做什麼變更，就開條新分支吧。畢竟，&lt;a href=&#34;https://gitbook.tw/chapters/branch/why-branch-is-cheap.html&#34;&gt;分支標籤不用錢，成本超低廉&lt;/a&gt;，各位還是在分支間跳來跳去吧，至少跳完了找得回來嘛。&lt;/p&gt;
&lt;p&gt;延伸閱讀：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://titangene.github.io/article/git-detached-head.html&#34;&gt;淺入 Git：detached HEAD - Titangene Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/faq/detached-head.html&#34;&gt;【冷知識】斷頭（detached HEAD）是怎麼一回事？ - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/branch/why-branch-is-cheap.html&#34;&gt;【冷知識】為什麼大家都說在 Git 開分支「很便宜」？ - 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&#34;關於-git-的分支策略&#34;&gt;關於 Git 的分支策略&lt;/h3&gt;
&lt;p&gt;既然是團隊合作，雖然大家各自拉了一條分支出去做事，但還是要有點 SOP，這就叫做分支策略。&lt;/p&gt;
&lt;p&gt;以最常見的 Git Flow 來說，&lt;strong&gt;會有兩個最重要的分支：正式服務的主要分支（master, main）、開發用的測試分支（develop）。接著再衍生出相關的分支，例如 功能（feature）、修復（hotfix）等等&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;當然針對這些分支，也會有一些相關的規定，主要會有命名規定和合併的規定。例如微軟的分支命名建議就比較像 &lt;code&gt;{分支種類}/{人員}/{描述}&lt;/code&gt;，例如說小明拉了一個產品功能的分支，就是 &lt;code&gt;feature/Ming/product&lt;/code&gt; 等等。&lt;/p&gt;
&lt;p&gt;流程的部分就有多個流派，目前我們常用的方式和上面提到的 Git Flow 比較相像，是從 master 拉出 develop，然後在 develop 上拉出多個 feature 進行開發。&lt;/p&gt;
&lt;p&gt;這些 feature 分支開發完畢後，再匯回 develop 進行測試，當專案完成、develop 的測試通過後，就可以推上 master。&lt;/p&gt;
&lt;p&gt;而當線上有問題發生的時候，會從 master 拉出 hotfix 分支，並修復完成之後同步更新給 master 和 develop。藉此保持 master 分支的穩定和乾淨。&lt;/p&gt;
&lt;p&gt;不過當然這是我所屬的團隊常用的方法，這些流程還是很吃團隊文化的，記得入境隨俗哪。&lt;/p&gt;
&lt;p&gt;關於這些分支的介紹和協作，可以參照這些文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/gitflow/why-need-git-flow.html&#34;&gt;Git Flow 是什麼？為什麼需要這種東西？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hsiangfeng.github.io/git/20200914/1124442109/&#34;&gt;淺談 Git Flow 與 commit 規範 | Welcome.Web.World (hsiangfeng.github.io)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，關於分支的處理和觀念，特別推薦這個系列，獲益良多：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E5%93%88%E5%98%8D-%E4%B8%96%E7%95%8C/%E5%9C%98%E9%9A%8A%E7%9A%84-git-%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86%E7%AD%96%E7%95%A5-449bc229c957&#34;&gt;團隊的 GIT 分支管理策略 (1) ： 基本概念&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E5%93%88%E5%98%8D-%E4%B8%96%E7%95%8C/%E5%9C%98%E9%9A%8A%E7%9A%84-git-%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86%E7%AD%96%E7%95%A5-2-%E6%95%B4%E5%90%88%E9%A0%BB%E7%8E%87%E5%B0%8D%E5%9C%98%E9%9A%8A%E6%95%88%E7%8E%87%E7%9A%84%E5%BD%B1%E9%9F%BF-bedbbdd3e70e&#34;&gt;團隊的 GIT 分支管理策略 (2) ： 主線整合與功能分支&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;什麼是-git-的遠端儲存庫remote要怎麼把變更推送push到遠端儲存庫&#34;&gt;什麼是 Git 的遠端儲存庫（Remote）？&lt;br/&gt;要怎麼把變更推送（Push）到遠端儲存庫？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;開始本章節之前，你可能需要先註冊好 &lt;a href=&#34;https://github.com/&#34;&gt;Github&lt;/a&gt;，註冊過程挺簡單的，不用擔心&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;既然都已經使用 Git 了，當然要推送到遠端儲存庫啦！作為儲存庫的服務有蠻多的，例如 Github、GitLab、Gitea 等等，本篇會以最知名的工程師交友網站 Github 為例。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;what&amp;rsquo;s the difference between git and github?&amp;rdquo; &lt;br/&gt;
&amp;ldquo;It&amp;rsquo;s the difference between porn and pornhub.&amp;rdquo;  (&lt;a href=&#34;https://www.reddit.com/r/github/comments/g5030w/&#34;&gt;Reddit&lt;/a&gt;)&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先讓我們在 Github 上新建一個儲存庫，從 My Repositories 或是首頁進去都可以：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/RXL3p7f.webp&#34;
  alt=&#34;&#34;width=&#34;390&#34; height=&#34;170&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們填寫一些基本訊息，像是專案名稱和專案敘述：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6b4sjKx.webp&#34;
  alt=&#34;&#34;width=&#34;1011&#34; height=&#34;763&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;底下會有一些選項，像是是否加入 .gitignore 啦、是否加入 readme（說明文檔）等等，這邊暫時還不會用到。&lt;/p&gt;
&lt;p&gt;完成後就可以按下 &lt;code&gt;Create Repository&lt;/code&gt;，接著就會來到專案啟動畫面：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ykHLsko.webp&#34;
  alt=&#34;&#34;width=&#34;1546&#34; height=&#34;790&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這個畫面已經說明了不同狀況下的操作，像我們已經在本機已經存在儲存庫了，所以我們可以參考「…or push an existing repository from the command line」這個部分進行操作。&lt;/p&gt;
&lt;p&gt;其中最重要的是上面那串 &lt;code&gt;.git&lt;/code&gt; 結尾的網址，我們接著就要使用這個網址來將剛剛的 &lt;code&gt;hello-git&lt;/code&gt; 資料夾推送到這個儲存庫。&lt;/p&gt;
&lt;p&gt;現在讓我們鏡頭回到棚內的 git 指令，首先 &lt;code&gt;git status&lt;/code&gt; 確保 Commit 等動作都已經完成：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Nci6Yvd.webp&#34;
  alt=&#34;&#34;width=&#34;504&#34; height=&#34;228&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們&lt;strong&gt;用 &lt;code&gt;git remote add&lt;/code&gt; 來增加叫做 &lt;code&gt;origin&lt;/code&gt; 的遠端儲存庫到這個儲存庫中&lt;/strong&gt;，完成了之後再用 &lt;code&gt;git remote&lt;/code&gt; 確認遠端儲存庫列表是不是已經有 &lt;code&gt;origin&lt;/code&gt; 了：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git remote add origin https://github.com/yourGithubName/yourGithubRepository.git
git remote
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WFqrEAu.webp&#34;
  alt=&#34;&#34;width=&#34;694&#34; height=&#34;239&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;因為 Github 的主要分支已經改成使用 &lt;code&gt;Main&lt;/code&gt; 了，因此我們順應一下，也用 &lt;code&gt;git branch -M&lt;/code&gt; 把 master 分支改名成 Main 吧&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git branch -M main
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vNFIUiQ.webp&#34;
  alt=&#34;&#34;width=&#34;537&#34; height=&#34;171&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;完成之後，就可以開始上傳囉。在 Git 中，&lt;strong&gt;推送到遠端儲存庫只要使用 &lt;code&gt;push&lt;/code&gt; 指令就可以了，語法是 &lt;code&gt;git push {遠端儲存庫名稱} {要推送的分支}&lt;/code&gt;&lt;/strong&gt;，例如我們現在的推送就是：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git push origin main
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Ubdt85H.webp&#34;
  alt=&#34;&#34;width=&#34;755&#34; height=&#34;378&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;推送完成後，讓我們再回到 Github 上查看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SNLyXDy.webp&#34;
  alt=&#34;&#34;width=&#34;1147&#34; height=&#34;376&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見東西都已經推送上去了。這時候按下旁邊的 Commit，也能夠查看這個儲存庫的 Commit Log 囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DY9AStm.webp&#34;
  alt=&#34;&#34;width=&#34;508&#34; height=&#34;581&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;到這邊就成功把存檔弄上雲端啦！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果儲存庫曾經有用 &lt;code&gt;reset&lt;/code&gt; 之類的往後跳，並且遠端儲存庫的版本又相對較新，例如遠端是第五版，抓下來之後 &lt;code&gt;reset&lt;/code&gt; 回到第三版，這時候的 &lt;code&gt;push&lt;/code&gt; 就會被擋下來，要求取得遠端最新的版本。&lt;/p&gt;
&lt;p&gt;諸如此類的狀況，造成無法推送的時候，如果 十、分、確、定 手上的這份才是對的，必須推送上去，可以使用 &lt;code&gt;-f&lt;/code&gt; 參數來進行強制推送。可以參見：&lt;a href=&#34;https://gitbook.tw/chapters/github/using-force-push.html&#34;&gt;狀況題】聽說 git push -f 這個指令很可怕，什麼情況可以使用它呢？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;不過說實在的，還是祈禱不會遇到需要 &lt;code&gt;-f&lt;/code&gt; 的那一天吧…&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;要怎麼從-git-的遠端儲存庫拿到變更認識擷取fatch提取pull&#34;&gt;要怎麼從 Git 的遠端儲存庫拿到變更？認識擷取（Fatch）、提取（Pull）&lt;/h2&gt;
&lt;p&gt;上傳到雲端已經沒問題了，那從雲端下載到本機呢？這時候我們就需要用到 &lt;code&gt;fetch&lt;/code&gt; 和 &lt;code&gt;pull&lt;/code&gt; 這兩個指令。&lt;/p&gt;
&lt;p&gt;現在讓我們回到 Github 的畫面，並且進入 A.txt&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/cVtjDCx.webp&#34;
  alt=&#34;&#34;width=&#34;529&#34; height=&#34;192&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後按下編輯：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9SU7PR1.webp&#34;
  alt=&#34;&#34;width=&#34;749&#34; height=&#34;373&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;把內文稍微修改一下，例如改成 &lt;code&gt;Hello X &amp;amp; Y &amp;amp; Z!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TRv8i93.webp&#34;
  alt=&#34;&#34;width=&#34;450&#34; height=&#34;208&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;拉到最下面，加上 Commit Message，並且按下 Commit Changes&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GRpH0el.webp&#34;
  alt=&#34;&#34;width=&#34;866&#34; height=&#34;522&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在遠端儲存庫已經有了一個變更，但是我們本機還不知道呢！接著就讓我們實際操作一下，首先我們需要取得遠端的資訊，這時候就可以&lt;strong&gt;用 &lt;code&gt;fetch&lt;/code&gt; 來擷取遠端儲存庫的資訊&lt;/strong&gt;回來：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git fetch
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/InviqZr.webp&#34;
  alt=&#34;&#34;width=&#34;680&#34; height=&#34;331&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見 Git 從遠端儲存庫抓了資訊回來，這時候我們如果查看 &lt;code&gt;git log&lt;/code&gt;，並加上 &lt;code&gt;--all&lt;/code&gt; 來顯示全部分支的話：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JBEalid.webp&#34;
  alt=&#34;&#34;width=&#34;633&#34; height=&#34;393&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見有一個 &lt;code&gt;origin/main&lt;/code&gt; 的分支超越了我們的 &lt;code&gt;main&lt;/code&gt; 分支了，這也就是在 &lt;code&gt;origin&lt;/code&gt; 遠端儲存庫的 &lt;code&gt;main&lt;/code&gt; 分支的意思。這時候我們就可以知道，遠端儲存庫的進度比起我們本機的還要更新。&lt;/p&gt;
&lt;p&gt;那麼要怎麼讓本機的進度追上呢？其實 &lt;code&gt;main&lt;/code&gt; 和 &lt;code&gt;origin/main&lt;/code&gt; 也就是兩條分支，所以只要使用 &lt;code&gt;merge&lt;/code&gt; 就可以把進度往前推。&lt;/p&gt;
&lt;p&gt;當然我們也可以不用這麼麻煩，可以直接&lt;strong&gt;使用 &lt;code&gt;pull&lt;/code&gt; 指令來提取遠端儲存庫對應的分支直接和本機現在的分支進行合併，也就是 &lt;code&gt;fetch&lt;/code&gt; + &lt;code&gt;merge&lt;/code&gt;&lt;/strong&gt;。這樣就是從雲端下載最新檔囉！現在就讓我們試試吧：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git pull origin main
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;pull&lt;/code&gt; 語法的邏輯和 &lt;code&gt;push&lt;/code&gt; 是一樣的。當我們 &lt;code&gt;pull&lt;/code&gt; 下來後，就可以 &lt;code&gt;git log&lt;/code&gt; 來確認一下記錄囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/945NY8u.webp&#34;
  alt=&#34;&#34;width=&#34;579&#34; height=&#34;581&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到我們的 &lt;code&gt;main&lt;/code&gt; 分支已經跟上 &lt;code&gt;origin/main&lt;/code&gt; 的進度囉！&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;使用-git-clone-直接把遠端儲存庫的-repo-抓下來&#34;&gt;使用 Git Clone 直接把遠端儲存庫的 Repo 抓下來&lt;/h2&gt;
&lt;p&gt;上面我們已經嘗試過「本機有儲存庫，上傳到遠端儲存庫」的場景了。但是大多數時候，像是我們在公司會需要接手開發專案啦、使用人家已經寫好的工具啦等等，都是「&lt;strong&gt;本機沒有任何東西，要從遠端進行下載&lt;/strong&gt;」的場景。&lt;/p&gt;
&lt;p&gt;這個時候就是 Github 大家最常做的 &lt;code&gt;Clone&lt;/code&gt; 出場的時候啦！&lt;/p&gt;
&lt;p&gt;現在就讓我們再新增一個 &lt;code&gt;GitRepos&lt;/code&gt; 資料夾，我們的目標就是把儲存庫拉下來這裡：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ryaEGB2.webp&#34;
  alt=&#34;&#34;width=&#34;358&#34; height=&#34;169&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們回到 Github 上的 Repository，畫面中間靠右會有一個綠色的 &lt;code&gt;↓ Code&lt;/code&gt; 按鈕（這應該是整個 Github 大家按最多次的按鈕）點開就會出現下載選項：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WY6HnKT.webp&#34;
  alt=&#34;&#34;width=&#34;517&#34; height=&#34;417&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;分別是各個命令列下載方式對應的連結、Github Desktop 專用的開啟方式，還有最常用到的 ZIP 下載方式。&lt;/p&gt;
&lt;p&gt;我個人是比較常下載壓縮檔來解壓縮啦，不過都已經用指令到這裡了，就讓我們來試試 &lt;strong&gt;&lt;code&gt;git clone&lt;/code&gt;&lt;/strong&gt; 吧：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git clone https://github.com/yourGithubName/yourGithubRepository.git
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TmPSidi.webp&#34;
  alt=&#34;&#34;width=&#34;718&#34; height=&#34;301&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著在 &lt;code&gt;GitRepos&lt;/code&gt; 裡面就生出來一個 &lt;code&gt;hello-git&lt;/code&gt; 啦，進入後檔案也都在呢：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qJ9RVQN.webp&#34;
  alt=&#34;&#34;width=&#34;526&#34; height=&#34;197&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就成功把儲存庫 Clone 下來囉！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clone 下來的儲存庫會自動建立和遠端儲存庫的繫結&lt;/strong&gt;，也就是已經是 &lt;code&gt;git remote add &lt;/code&gt; 好的狀態，相當方便。接著後續就和基本的 Git 操作一樣囉。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;認識-git-的提取要求pull-request-pr&#34;&gt;認識 Git 的提取要求（pull request, PR）&lt;/h2&gt;
&lt;p&gt;如果你 Clone 的是自己的儲存庫，當然就可以開始工作後 &lt;code&gt;Add&lt;/code&gt; &lt;code&gt;Commit&lt;/code&gt; &lt;code&gt;Push&lt;/code&gt; 連發，但如果不是你自己的儲存庫，又想要幫忙修改東西，或是工作上有審核機制，該怎麼辦呢？&lt;/p&gt;
&lt;p&gt;這個時候我們就要用提取要求（pull request）的方式來互動啦。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pull request 就是指發出一個 request 請對方來 Pull，如果對方覺得 OK，就會同意提取要求並把變更合併到自己的儲存庫裡&lt;/strong&gt;。不過發 PR 這個動作似乎在每個平台都有點微妙的不同。&lt;/p&gt;
&lt;p&gt;例如說平常工作時候使用 TFS（現在叫做 Azure DevOps）的發 PR 方式，是將分支推送上去之後，發起 PR 並請主管審核，確認沒問題後再進行 Merge；但在 Github 上，則是要先叉（Fork）一份到自己家，Clone 下來修改完之後推上去，再發出 PR 請對方來提取合併。&lt;/p&gt;
&lt;p&gt;PR 比較常見於社群協作，還有工作上的提交審核等等，也由於這個部分已經是社群互動的進階動作了，故先按下不表。附上 Azure DevOps 和 Github 的 Pull Request 操作方式的介紹文章，有興趣的朋友可以去看看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/github/pull-request.html&#34;&gt;與其它開發者的互動 - 使用 Pull Request（PR）- 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.alantsai.net/posts/2019/05/code-review-02-what-is-pull-request-and-how-to-create-it-in-azure-devops&#34;&gt;[02][讓團隊彼此知道程式碼走向]何爲Pull Request並且如何建立 - 以Azure DevOps爲例&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;本來只是想介紹一下 Git 的一些基本操作，沒想到越弄越長（汗），儘管如此，還是有很多說明不到的地方（例如提取要求、分支策略等等），留待各位實戰的時候細細體會了。&lt;/p&gt;
&lt;p&gt;最後還是要說句，真的要買一本為你自己學 Git 放在手邊哪，有夠實用。感謝作者，感謝網路上的各位大大，感謝公司圖書櫃，各位一生平安。&lt;/p&gt;
&lt;p&gt;那麼，這篇就先到這裡啦，這個系列終於還是開坑了，希望這次也能順順利利了…吧。那麼，我們下次見！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;本系列文章&#34;&gt;本系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/&#34;&gt;為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://backlog.com/git-tutorial/tw/&#34;&gt;連猴子都能懂的 Git 入門指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789862766699&#34;&gt;版本控制使用 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/users/20004901/ironman/525&#34;&gt;30 天精通 Git 版本控管&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git-scm.com/book/zh-tw/v2&#34;&gt;Pro Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/category/Git&#34;&gt;黑暗執行緒的 Git 分類文章&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/my-git-cheatsheet/&#34;&gt;黑暗執行緒的 Git 指令筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.slmt.tw/blog/2016/08/21/dont-expose-your-git-dir/&#34;&gt;SLMT&amp;rsquo;s Blog | 漏洞筆記 - 別讓你的 .git 資料夾公開在網路上啊！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/05/commit-commit-commit-why-what-commit.html&#34;&gt;Git Commit Message 這樣寫會更好，替專案引入規範與範例 (wadehuanglearning.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/&#34;&gt;如何寫一個 Git Commit Message | louie_lu&amp;rsquo;s blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.1ju.org/git/git-diff&#34;&gt;git diff命令 - Git教程教學 | 程式教程網 (1ju.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10135441&#34;&gt;30 天精通 Git 版本控管 (09)：比對檔案與版本差異 - iT 邦幫忙 (ithome.com.tw)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git-scm.com/book/zh/v2/Git-%E5%9F%BA%E7%A1%80-%E6%9F%A5%E7%9C%8B%E6%8F%90%E4%BA%A4%E5%8E%86%E5%8F%B2&#34;&gt;Git - 查看提交历史 (git-scm.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/diff2html-webpage/&#34;&gt;Git 筆記 - 產生程式異動對照表(Compare List)-黑暗執行緒 (darkthread.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://awdr74100.github.io/2020-04-27-git-diff/&#34;&gt;Git 版本控制系統 - 比對檔案版本差異與標示說明 | Roya&amp;rsquo;s Blog (awdr74100.github.io)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://w3c.hexschool.com/git/9a164fbe&#34;&gt;git checkout 移動 HEAD 指標 - Git 分支(branch) | W3HexSchool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://titangene.github.io/article/git-head-ref.html&#34;&gt;深入 Git：HEAD refs | Titangene Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://titangene.github.io/article/git-detached-head.html&#34;&gt;淺入 Git：detached HEAD | Titangene Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.liaoxuefeng.com/wiki/896043488029600/896202780297248&#34;&gt;集中式vs分布式 - 廖雪峰的官方网站&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/starbugs/use-git-interactive-rebase-to-organize-commits-85e692b46dd&#34;&gt;送 PR 前，使用 Git rebase 來整理你的 commit 吧！ - 星巴哥技術專欄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/FollowGodSteps/article/details/96271359&#34;&gt;PowerShell | git log 中文乱码问题解决&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://jamestw.logdown.com/posts/238719-advanced-git-log&#34;&gt;Git log 進階應用 - Jame’s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kingofamani.gitbooks.io/git-teach/content/chapter_3_branch/stash.html&#34;&gt;Stash暫存 · GIT教學&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2019/11/git-stash.html&#34;&gt;菜鳥工程師 肉豬: Git stash 暫存正在修改的內容&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/i-think-so-i-live/git%E4%B8%8A%E7%9A%84%E4%B8%89%E7%A8%AE%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B-10f4f915167e&#34;&gt;Git上的三種工作流程 - 儲思盆 | Pensieve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git-tutorial.readthedocs.io/zh/latest/branchingmodel.html&#34;&gt;Git flow 分支策略 - Practical guide for git users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/git-branching-strategies/&#34;&gt;TFS Git 筆記 - 分支管理策略 - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.pianshen.com/article/30471944392/&#34;&gt;Git flow 分支管理策略 - 程序員大本營&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E5%93%88%E5%98%8D-%E4%B8%96%E7%95%8C/%E5%9C%98%E9%9A%8A%E7%9A%84-git-%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86%E7%AD%96%E7%95%A5-449bc229c957&#34;&gt;團隊的 GIT 分支管理策略 (1) ： 基本概念&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E5%93%88%E5%98%8D-%E4%B8%96%E7%95%8C/%E5%9C%98%E9%9A%8A%E7%9A%84-git-%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86%E7%AD%96%E7%95%A5-2-%E6%95%B4%E5%90%88%E9%A0%BB%E7%8E%87%E5%B0%8D%E5%9C%98%E9%9A%8A%E6%95%88%E7%8E%87%E7%9A%84%E5%BD%B1%E9%9F%BF-bedbbdd3e70e&#34;&gt;團隊的 GIT 分支管理策略 (2) ： 主線整合與功能分支&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/gitflow/why-need-git-flow.html&#34;&gt;Git Flow 是什麼？為什麼需要這種東西？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hsiangfeng.github.io/git/20200914/1124442109/&#34;&gt;淺談 Git Flow 與 commit 規範 | Welcome.Web.World (hsiangfeng.github.io)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitbook.tw/chapters/github/pull-request.html&#34;&gt;與其它開發者的互動 - 使用 Pull Request（PR）- 為你自己學 Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.alantsai.net/posts/2019/05/code-review-02-what-is-pull-request-and-how-to-create-it-in-azure-devops&#34;&gt;[02][讓團隊彼此知道程式碼走向] 何爲Pull Request並且如何建立 - 以Azure DevOps爲例&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞新訓記 (0): 前言</title>
      <link>https://igouist.github.io/post/2021/04/newbie-0-menu/</link>
      <pubDate>Mon, 05 Apr 2021 22:39:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/04/newbie-0-menu/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/uobV40z.webp&#34;
  alt=&#34;img&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;長夜將至，我從今開始守望。&lt;br/&gt;
　　　　　　　　　　　　　　　　　　　　　　　　　——《冰與火之歌》守夜人誓詞&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;年初整理完物件導向系列後，休息（沉迷遊戲）了好一陣子，終於要繼續整理公司新訓的內容啦！&lt;/p&gt;
&lt;p&gt;因為這個系列會是公司新訓時期的筆記整理，所以會是比較簡易的實作紀錄，並不會太過深入，需要的時候會用延伸閱讀的形式補充上去。如果看文的過程中覺得有什麼能夠補充的，也歡迎告訴我呦。&lt;/p&gt;
&lt;p&gt;本系列預計會從 Git 的基本操作開始，簡單建立一個 Web Api 為主軸，逐步介紹相關的部份，例如簡單地引入套件、簡單地分層等等。基本方針就是直接抄襲 &lt;a href=&#34;https://sunnyday0932.github.io/&#34;&gt;隔壁同事的部落格&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;後續有更新的文章，就會整理到這篇目錄中。或是也可以從 &lt;a href=&#34;https://igouist.github.io/series/%E8%8F%9C%E9%9B%9E%E6%96%B0%E8%A8%93%E8%A8%98/&#34;&gt;菜雞新訓記&lt;/a&gt; 裡面做系列文的查詢。&lt;/p&gt;
&lt;p&gt;那麼，就從第一篇：&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;Git 入門這樣做&lt;/a&gt; 開始吧！&lt;/p&gt;
&lt;h2 id=&#34;本系列文章&#34;&gt;本系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-0-menu&#34;&gt;菜雞新訓記 (0): 目錄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/04/newbie-1-hello-git&#34;&gt;菜雞新訓記 (1): 使用 Git 來進行版本控制吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;菜雞新訓記 (2): 認識 Api &amp;amp; 使用 .net Core 來建立簡單的 Web Api 服務吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-3-dapper&#34;&gt;菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-4-swagger&#34;&gt;菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/10/newbie-5-3-layer-architecture&#34;&gt;菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2022/03/newbie-7-fluent-validation&#34;&gt;菜雞新訓記 (7): 使用 FluentValidation 來驗證傳入參數吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>《隻狼》＆《黑暗靈魂３》白金紀念</title>
      <link>https://igouist.github.io/post/2021/03/sekiro-darksouls3-clear/</link>
      <pubDate>Sat, 13 Mar 2021 09:38:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/03/sekiro-darksouls3-clear/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CHKaup8.webp&#34;
  alt=&#34;Image&#34;width=&#34;1086&#34; height=&#34;487&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;




&lt;img
  src=&#34;https://image.igouist.net/hzoyQ1T.webp&#34;
  alt=&#34;Image&#34;width=&#34;1083&#34; height=&#34;494&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;苦難終於告一段落了，開心到 PO 一篇來紀念一下！&lt;/p&gt;
&lt;p&gt;接下來…… 就坐等血源詛咒上 PC 啦（等得到嗎？）&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀&#34;&gt;延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://game.udn.com/game/story/10451/3757216&#34;&gt;就是要逼你正面對決 《隻狼》如何用設計讓玩家展現勇氣？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://opinion.udn.com/opinion/story/6068/3028754&#34;&gt;《血源詛咒》如何善用內在動機讓玩家甘願受虐？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Visual Studio: 書籤 (bookmarks)</title>
      <link>https://igouist.github.io/post/2021/03/visual-studio-bookmark/</link>
      <pubDate>Sat, 13 Mar 2021 00:38:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/03/visual-studio-bookmark/</guid>
      <description>&lt;p&gt;今天從同事們那邊學到了書籤這個方便功能，趁還記得的時候來做個紀錄。&lt;/p&gt;
&lt;p&gt;那麼馬上就來操作一次：&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;Ctrl K&lt;/code&gt;, &lt;code&gt;Ctrl K&lt;/code&gt; 可以在指定的行號上加上一個「書籤」&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/uSBHhGt.webp&#34;
  alt=&#34;&#34;width=&#34;597&#34; height=&#34;312&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著我們就能在書籤視窗中看到這個書籤（檢視＞書籤視窗，或是 &lt;code&gt;Ctrl K&lt;/code&gt;, &lt;code&gt;Ctrl W&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/zyouiFg.webp&#34;
  alt=&#34;&#34;width=&#34;283&#34; height=&#34;215&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在書籤視窗中，也可以對書籤重新命名，或是建立資料夾進行管理&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MELQhKI.webp&#34;
  alt=&#34;&#34;width=&#34;327&#34; height=&#34;241&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而當頁面中有多個書籤的時候，就可以利用 &lt;code&gt;Ctrl K&lt;/code&gt;, &lt;code&gt;Ctrl P&lt;/code&gt; 和 &lt;code&gt;Ctrl K&lt;/code&gt;, &lt;code&gt;Ctrl N&lt;/code&gt; 來在書籤中移動&lt;/p&gt;
&lt;p&gt;這個快速移動能用在什麼時候呢？&lt;/p&gt;
&lt;p&gt;我們在進行一個專案時，會常常需要在多個檔案，或是在一個較長的檔案的各處來來回回。&lt;/p&gt;
&lt;p&gt;例如 private 方法離使用到的 public 方法有點遠，又或是我個人就常常會寫某個 Function 到一半的時候，突然發現：「啊！我忘記在建構式的時候把要用的對象傳進來了。」&lt;/p&gt;
&lt;p&gt;這時候就需要來回跑確認方法內容，或是移動到整個類別的頂部去增加宣告和修改建構式，接著再回到剛剛撰寫中的方法，在程式碼裡面折返跑，好不愉快。&lt;/p&gt;
&lt;p&gt;以往遇到這種狀況，我會稍微記一下行號，再利用 &lt;code&gt;Ctrl G&lt;/code&gt; 跳回去。&lt;/p&gt;
&lt;p&gt;今天跟同事聊到書籤的時候，就想到：像是上面的場景，便可以利用書籤來紀錄常用的幾個地方，並快速切換。並且用法也簡單直覺。&lt;s&gt;大概就像&lt;a href=&#34;https://baike.baidu.com/item/%E9%A3%9E%E9%9B%B7%E7%A5%9E%E4%B9%8B%E6%9C%AF&#34;&gt;飛雷神之術&lt;/a&gt;一樣吧。&lt;/s&gt;這邊就記錄一下，也分享給大家。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/visualstudio/ide/setting-bookmarks-in-code?view=vs-2019&#34;&gt;設定程式碼書籤 - Visual Studio | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (Ex1): 小結</title>
      <link>https://igouist.github.io/post/2021/01/oo-ex1-end2020/</link>
      <pubDate>Fri, 01 Jan 2021 23:50:49 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2021/01/oo-ex1-end2020/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hycMTRZ.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;隨著 2020 進到了 2021，菜雞與物件導向也推進到了五大原則結束，可以暫時告一段落了。&lt;/p&gt;
&lt;p&gt;最初和朋友約好了要把公司新訓學到的東西做個整理（廣告一下他的部落格：&lt;a href=&#34;https://sunnyday0932.github.io/&#34;&gt;Sian&lt;/a&gt;），只是沒想到因為太常發廢文偷懶，大半年才推進到物件導向的基礎而已，甚至占不到新訓內容的十分之一。希望 2021 能繼續推進，把這部份的坑給填一填，然後把前面的文章也重構一下，只是按照我的個性，可能又會忍不住開新的坑吧，哈哈。&lt;/p&gt;
&lt;p&gt;至於物件導向相關的心得和紀錄，也就是這個系列，偶而有想到或是有所體悟的時候再發上來吧，暫時想先把前面隨手寫凌亂文章給整一整先。在這個十幾篇的短系列，記錄了從類別與物件開始，到耦合、內聚及五大原則，每個主題的心得。這邊稍微做個小整理：&lt;/p&gt;
&lt;h3 id=&#34;類別物件&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;類別、物件&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;物件就是用來表達「我們知道的某個東西」，&lt;strong&gt;物件導向是用物件彼此互動的方式來建立架構&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;類別去定義我們要的物件有什麼特徵、有什麼功能，再從類別中實例化（也就是根據設計圖產生）物件出來使用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;建構式多載&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;建構式、多載&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;建構式用來在建立物件時就進行一些我們想要的操作，例如狗狗的毛色等等天生的東西，或是這個建立這個物件的必須素材&lt;/li&gt;
&lt;li&gt;多載指的就是可以有很多個同樣名字的方法，各自去接不同的參數，讓同個目標的函式可以&lt;strong&gt;根據傳入的參數不同做不一樣的處理&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;封裝&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;封裝&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;封裝將物件視作一個整體，讓每個物件保留自己的隱私。&lt;strong&gt;將物件的實作內容隱藏起來&lt;/strong&gt;，互動的其他物件和使用者只需要知道怎麼使用即可&lt;/li&gt;
&lt;li&gt;兩個物件彼此了解越多，耦合就會越高。因此藉由封裝，我們提高類別內的內聚性，降低對外的耦合性，隱藏複雜資訊，並且迴避掉一些不小心直接用了不該用的方法造成的問題&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;繼承&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;繼承&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;繼承是一種「is-a」的關係。當你能說出Ａ是一個Ｂ的時候，就代表你認為Ａ可以繼承自Ｂ
&lt;ul&gt;
&lt;li&gt;最直覺的繼承例子就是物種的分類。舉例來說，狗跟貓都是哺乳類，因此他們都可以繼承到一些哺乳類共通的特徵（例如哺乳、用肺呼吸）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;繼承讓我們能只修改基底類別，便讓所有衍生出來的類別都一併受用，&lt;strong&gt;大大提高了程式碼的重複使用性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;但是，同時也帶來了&lt;strong&gt;強耦合&lt;/strong&gt;，讓我們在修改基底類別時，不容易察覺到衍生類別因為這次修改而連帶發生的問題&lt;/li&gt;
&lt;li&gt;謹慎使用，或是乾脆不要用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;多型&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;多型&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;多型允許繼承同一個父類別的各個子類別可以用不同方式去實現父類別的要求。藉著用子類別實作出各式各樣不同的方法，就能&lt;strong&gt;讓父類別的方法達到延伸和多樣化的效果&lt;/strong&gt;，而不需要更動父類別本身&lt;/li&gt;
&lt;li&gt;同時，當子類別被以父類別的名義建立出來時，他就只能夠表現出父類別的樣子。使用者基於封裝的精神，不需要知道實作的細節&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;抽象覆寫&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;抽象、覆寫&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;這邊的抽象指的是抽象類別，也就是無法被實例化的類別，通常我們會用來宣告那些不應該被實體化成一個物件的類別，例如哺乳類&lt;/li&gt;
&lt;li&gt;覆寫是指對於像是前述的抽象方法，或是一些父類別定義好要求子類別必須重新實作的虛擬方法時，子類別繼承後必須重新去實作該方法，藉此達到多型&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;介面&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;介面就像是針對類別的實作、物件的行為去做規定的一個契約書，只需要先定義好該做的事，裡面怎麼做不需要管；所以只需要宣告要求的方法，不需要撰寫方法本體&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;介面將物件之間的互動的著眼點放在該物件的行為上&lt;/strong&gt;，降低了物件之間的耦合，更提高的物件彼此替換的彈性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;內聚耦合&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;內聚、耦合&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;內聚是指：把需要的程式和資料都包裝在同一個模組內，使得該模組能夠做為一個單獨的個體執行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;內聚代表的是該模組的獨立性&lt;/strong&gt;，當這個模組可以獨力完成工作，就代表我們能夠重複使用它，且不需要擔心影響到其他模組&lt;/li&gt;
&lt;li&gt;追求高內聚是絕對必要的，但達到完全內聚是不應該的，容易創造神之類別或大量複製貼上，使程式碼難以重複使用&lt;/li&gt;
&lt;li&gt;耦合是指：&lt;strong&gt;如果模組和另一個模組有關聯，那這兩者之間就耦合&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;耦合會讓物件之間彼此相關，容易導致改東壞西的狀況，提高修改的難度。但因為物件導向讓物件之間協作的精神，達到毫無耦合是不可能的&lt;/li&gt;
&lt;li&gt;彼此關聯就會彼此牽連，因此我們要&lt;strong&gt;讓物件彼此之間保持一個舒適的距離&lt;/strong&gt;。注意，是舒適的距離，而不是不相往來，健康的內聚就是健康的耦合&lt;/li&gt;
&lt;li&gt;判斷健康的內聚和耦合的標準，取決於該模組是否符合單一職責原則&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;solid&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;SOLID&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;隨著物件導向的亂用、誤用、無腦用，軟體就會逐漸腐化。因此，大前輩們整理並提出了一些可以致力的方向，也就是所謂的「原則」&lt;/li&gt;
&lt;li&gt;藉由這些原則，我們可以讓程式碼「&lt;strong&gt;能容忍變化、容易理解、能讓模組和元件使用&lt;/strong&gt;」，達到降低程式碼的腐化臭味的目標&lt;/li&gt;
&lt;li&gt;整個 SOLID 就是面對變化的作戰策略&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;單一職責原則&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;單一職責原則&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;顧名思義，就是一個類別應該只負責一個職責，但是職責的定義和大小容易產生誤會。因此實際定義是：&lt;strong&gt;就一個類別而言，應該只有一個引起它變化的原因&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引起變化的原因可能是業務需求的變更或追加，注意不要讓類別做它不該做或好像超出範圍的事情&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;把工作交給負責該職責的類別去做，自己只需要關注在自己正在處理的職責即可，也就是封裝的體現&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;單一職責的關鍵在於展現程式碼的意圖&lt;/strong&gt;，並時常自問：是否只有一個原因造成改變？職責是否清晰？&lt;/li&gt;
&lt;li&gt;當我們並未遵守單一職責原則時，會容易產生意外的重複、修改時也無法界定影響範圍、並且每次修改都要閱讀大量無關程式碼，使得修改成本大幅上升&lt;/li&gt;
&lt;li&gt;而遵守單一職責時，我們提高的程式碼的重複使用率，降低修改成本，並且也提高了內聚、降低了耦合，讓程式更容易管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;開放封閉原則&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;開放封閉原則&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;軟體實體（類別、模組、函式等等）&lt;strong&gt;應該對擴展開放，而對修改封閉&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;面對需求，對程式碼的改動是透過增加新程式碼進行的，而不是更改現有的程式碼&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;開放封閉原則的關鍵在於模組化&lt;/strong&gt;，以及準備擴充點。藉由單一職責切分出模組，並在主要邏輯和附加邏輯之間，加入抽象層來解耦合，可以讓物件之間保持彈性&lt;/li&gt;
&lt;li&gt;目標在於降低修改成本和風險，凡是變化都有成本，但如果我們做好模組化，並事先準備面對修改的策略，就可以降低修改的風險&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;里氏替換原則&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;里氏替換原則&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;子類別必須能夠替換父類別，不需要改變，也不會發生任何錯誤或異常&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;我們預期了這個函式或類別需要準備的輸入參數，也預期了應該要有的輸出結果。如果某一天替換了子類別，卻不是這麼一回事，就會發生很多意料外的錯誤&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;里氏替換原則的關鍵在於繼承時必須遵守三個條件：先驗條件不可以強化、後驗條件不可以弱化、不變條件必須保持不變&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;繼承的時候應該著眼在能否做出父類別期望的行為，而非子類別是否所屬於父類別（企鵝不會飛，鳥會飛）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;介面隔離原則&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;介面隔離原則&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不應該強迫用戶依賴它們未使用的方法；應該最小化類別與類別之間的介面&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;介面也必須要通過單一職責的考驗，在定義介面時，&lt;strong&gt;優先從組合的方面進行考慮，把想要的行為用職責的角度去思考&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;依賴反轉原則&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;依賴反轉原則&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高階模組不應該依賴於低階模組。兩者都應該依賴抽象&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ａ模組直接受到Ｂ模組的影響，我們就稱Ａ依賴了Ｂ。同時，抽象不應該依賴細節；細節應該依賴抽象，因此我們需要用抽象層將依賴做隔離&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依賴反轉原則的關鍵在於抽象層的觀念&lt;/strong&gt;：並不是低階模組的實作提供了抽象層，而是高階模組針對所需要的功能提出了抽象，而低階模組去實現它&lt;/li&gt;
&lt;li&gt;針對反轉之後如何建立實例的問題，&lt;strong&gt;控制反轉&lt;/strong&gt;是一個常見的思路：讓控制反轉中心去建立低階模組，然後高階模組要使用的時候再把這個低階模組交給高階模組使用，這個過程的實現方法就叫做&lt;strong&gt;依賴注入&lt;/strong&gt;。藉此讓高階模組處於控制權上的被動，同時也不用再負責建立低階模組，解除兩者間的依賴&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;最少知識原則&#34;&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;最少知識原則&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;只和直接的朋友溝通，不和陌生人說話，&lt;strong&gt;一個物件應該對其他物件應該只有最少的了解&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;直接的朋友包括：該方法所屬的類別、該方法所接收的參數、該方法中建立的類別、該方法所屬的類別所依賴的對象&lt;/li&gt;
&lt;li&gt;換個方向想就是直接具有依賴關係的類別和方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不應該使用其他類別的方法所回傳的類別的方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最少知識原則的關鍵在於良好的封裝&lt;/strong&gt;：複雜性隱藏到自己內部，對外只開放必要的功能，並且只使用到直接關聯的對象，確保不會造成意外的耦合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;當然，還有許多沒有收錄或是我也尚未學習到的部分，就先收錄在本篇結尾的遺珠之憾單元。如果有什麼體悟了，或是累積了一些數量（也就是之後腦洞又開了）就再拿來發新文章，如此豈不樂哉。如果對內容有什麼想法或能補充的，有發現我有所疏漏的地方，也還麻煩跟我說一聲，這邊就對那些願意給予幫助、以及把這些文章看完的朋友道個謝囉。&lt;/p&gt;
&lt;p&gt;那麼，我們下個系列再見囉，各位新年快樂！&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;lt;/2020&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;遺珠之憾&#34;&gt;遺珠之憾&lt;/h2&gt;
&lt;p&gt;這邊放一些物件導向相關的咚咚和推薦文章，彌補本篇一些沒提到的部分，例如合成複用原則、三次原則等等。當然，有想到也會回來補充一下啦～&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10236782&#34;&gt;[Day08] 合成/聚合複用原則 | Composite/Aggregate Reuse Principle - iT 邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://shawnlin0201.github.io/Methodology/Methodology-004-Rule-Of-Three-principle/&#34;&gt;程式設計心法 三次原則（Rule Of Three principle）- 璇之又璇的網路世界&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://shawnlin0201.github.io/Methodology/Methodology-001-DRY-principle/&#34;&gt;程式設計心法 避免重複原則（DRY principle）- 璇之又璇的網路世界&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.ruanyifeng.com/blog/2013/01/abstraction_principles.html&#34;&gt;代码的抽象三原则&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (15): 最少知識原則</title>
      <link>https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle/</link>
      <pubDate>Sun, 20 Dec 2020 23:57:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/FOWZ8zY.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;上一篇&lt;/a&gt;我們紀錄了依賴反轉原則，到此五大原則介紹完畢…是這樣嗎？太天真了！就像四天王總是五個人一樣，五大原則當然也有第六個！&lt;/p&gt;
&lt;p&gt;今天的主角就是五大原則中Ｌ位的第一候補：&lt;strong&gt;最少知識原則&lt;/strong&gt;，也被稱作&lt;strong&gt;迪米特法則&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;最少知識原則-least-knowledge-principle&#34;&gt;最少知識原則 (Least Knowledge Principle)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;只和直接的朋友溝通，不和陌生人說話&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;那麼所謂的朋友是什麼呢？就是指這個物件或方法有直接相關的物件啦。例如當我們使用一個方法時，這個方法應該只認識：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;該方法所屬的類別&lt;/li&gt;
&lt;li&gt;該方法所接收的參數&lt;/li&gt;
&lt;li&gt;該方法中建立的類別&lt;/li&gt;
&lt;li&gt;該方法所屬的類別所依賴的對象&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除此之外對這個方法而言都是陌生人。什麼情況會遇到陌生人呢？有一個蠻常遇到的狀況就符合定義：當我們使用依賴對象的方法，該方法給了我們另一個類別時，我們就正在接觸毫無關係的陌生人。&lt;/p&gt;
&lt;p&gt;這個原則的要求就是：不要跟陌生人說話，就算是朋友介紹了他的朋友給你也一樣，不認識就是不認識，更不能拿陌生人的東西。換個方式就是說：&lt;strong&gt;不應該使用其他類別的方法所回傳的類別的方法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;用文字的可能會有點繞口令，簡單來說就是像 &lt;code&gt;Foo.GetBoo().BooDoSomeThing()&lt;/code&gt; 這種情況，我們不該去跟 &lt;code&gt;Foo&lt;/code&gt; 要 &lt;code&gt;Boo&lt;/code&gt; 回來然後使用 &lt;code&gt;Boo&lt;/code&gt; 的方法，因為我們只認識 &lt;code&gt;Foo&lt;/code&gt;，而不認識 &lt;code&gt;Boo&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;畢竟，很多時候我們不該直接插手控制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主人可以叫狗坐下，但主人不應該直接控制狗的腿坐下&lt;/li&gt;
&lt;li&gt;當我們按下牆壁的開關時，是希望燈直接打開。而不是彈出兩條電線讓你自己接起來&lt;/li&gt;
&lt;li&gt;當我們去餐廳時，會讓服務生替你把要求的餐點交給廚師烹調，而不是我們直接殺進去廚房對著廚師吼「你給我煮啊！」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這種直接叫廚師煮給你看、甚至自己搶過來煮的做法，就是平常直接伸手進去其他模組的控制狂、完全和 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;封裝&lt;/a&gt; 的概念背道而馳。&lt;/p&gt;
&lt;p&gt;腿的動作就應該讓狗去自己控制，讓燈泡亮就應該隱藏在開關之後。物件就該只和直接的朋友溝通。&lt;/p&gt;
&lt;p&gt;除了只和直接的朋友溝通，也就是只和直接依賴的類別互動，這個互動也是要講究一點的。畢竟朋友之間也還是會有共通的默契和距離，類別之間的互動也應該只做必要的溝通。&lt;/p&gt;
&lt;p&gt;這就是我們在封裝提過的「給程式碼隱私的空間」：為了避免物件之間的互動情況過於複雜，我們應該加以控制，把各自的工作封裝在各自的物件內部，使其只有必要的往來。&lt;/p&gt;
&lt;p&gt;因此最少知識原則就要求了：&lt;strong&gt;一個物件應該對其他物件應該只有最少的了解&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;到這邊讓我們稍微整理一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;只和直接的朋友溝通，不和陌生人說話&lt;/strong&gt;：&lt;br/&gt;物件或方法應該只和自己及直接接觸的對象互動&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不應該使用其他類別的方法回傳的類別的方法&lt;/strong&gt;：&lt;br/&gt;不該破壞封裝並造成額外且違反邏輯的互動&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一個物件對其他物件應該只有最少的了解&lt;/strong&gt;：&lt;br/&gt;類別只開放 (Public) 必要的功能，並且類別之間應該只有必要的互動&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是說：&lt;strong&gt;只依賴應該依賴的對象，只開放應該開放的方法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;聰明的朋友應該能從這邊看出最少知識原則的核心理念了，就是&lt;strong&gt;解除耦合&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我們在 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;耦合篇&lt;/a&gt; 提過，物件彼此有關聯就會產生耦合，而不好的耦合就會散發出臭味。為了方便管理和降低複雜性，減少臭味出現的機率，我們的目標就是追求耦合。&lt;/p&gt;
&lt;p&gt;相對於 &lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;依賴反轉原則&lt;/a&gt; 利用 &lt;a href=&#34;https://igouist.github.io/post/202007-oo-7-interface&#34;&gt;抽象和介面&lt;/a&gt; 的方式在模組之間做出隔離和控制的作法。最少知識原則則是利用 &lt;a href=&#34;https://igouist.github.io/post/202007-oo-3-encapsulation&#34;&gt;封裝&lt;/a&gt; 的概念來解除耦合，畢竟，關聯越少耦合也越少嘛。&lt;/p&gt;
&lt;p&gt;所以我們可以說：良好的封裝就是符合最少知識原則的封裝。複雜性隱藏到自己內部，對外只開放必要的功能，並且只使用到直接關聯的對象，確保不會造成意外的耦合，且讓關聯的模組之間更加靠攏。如此一來，就能夠更加提高內聚、降低耦合了。&lt;/p&gt;
&lt;p&gt;然而，為了好好地切分朋友和陌生人，也可能會變成需要&lt;strong&gt;建立更多的中間類別&lt;/strong&gt;，或是更多的依賴關係。&lt;/p&gt;
&lt;p&gt;例如人原本可以直接把電線接起來讓燈泡亮起來，但為了把電線使燈泡變亮這件事的複雜度封裝起來，我們就必須要有一個開關，再把電線放到開關後面去，變成了人按下開關，開關藉由電線點亮燈炮等等，整體來說會使系統內的類別變多。&lt;/p&gt;
&lt;p&gt;因此，在設計的時候也必須要考量到整個方法串的深度，可以用 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;單一職責&lt;/a&gt; 的角度下去衡量。請不要越封裝越細，類別越做越多，反而變成過度設計了。&lt;/p&gt;
&lt;p&gt;那麼，今天就記錄到這裡。由於最少知識原則的概念，大多在封裝篇和耦合篇的時候就已經偷渡完了，所以這邊就針對觀念簡單介紹，實務上處理類別間的耦合時，就可以稍微從最少知識原則的角度想一想，一定會有幫助的。那麼，我們下次見～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/hatelove/2010/10/16/least-knowledge-principle&#34;&gt;[ASP.NET]91之ASP.NET由淺入深 不負責講座 Day19 - LoD/LKP 最少知識原則 - In 91&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ckpattern35/ck-patt-%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F-11-%E8%BF%AA%E7%B1%B3%E7%89%B9%E6%B3%95%E5%89%87-demeter-law-931fefc4abda&#34;&gt;[CK Patt 設計模式#11] 迪米特法則(Demeter Law)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://yuchitung.github.io/2019/06/24/least-knowledge-principle/&#34;&gt;最小知識原則 - Yuchi 的學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-3_law-of-demeterlod-%E7%8B%84%E7%B1%B3%E7%89%B9%E6%B3%95%E5%89%87/&#34;&gt;Object Oriented物件導向設計原則SOLID-3:Law of Demeter(LoD) 狄米特法則 - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://c.biancheng.net/view/1331.html&#34;&gt;迪米特法則——面向對象設計原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/gaochundong/p/least_knowledge_principle.html&#34;&gt;最少知识原则（Least Knowledge Principle） - 熵碼匠藝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.ithome.com.tw/voice/98670&#34;&gt;封裝與迪米特法則 - 林信良&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (14): 依賴反轉原則</title>
      <link>https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle/</link>
      <pubDate>Sun, 13 Dec 2020 21:53:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ywiHuis.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#依賴與耦合&#34;&gt;依賴與耦合&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#依賴反轉原則-dependency-inversion-principle&#34;&gt;依賴反轉原則 (Dependency-Inversion Principle)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#控制反轉-inversion-of-control-ioc--依賴注入-dependency-injection&#34;&gt;控制反轉 (Inversion of Control, IoC) &amp;amp;&lt;br/&gt; 依賴注入 (Dependency Injection)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#結語&#34;&gt;結語&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#同系列文章&#34;&gt;同系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;p&gt;在聊依賴反轉之前，先讓我們聊聊什麼是依賴：&lt;/p&gt;
&lt;p&gt;例如說如果有個像我一樣的肥宅每天一定要來一片雞排才能療癒身心，那我就是依賴雞排；&lt;br/&gt;
同樣的，如果有個大叔不抽菸就會全身不舒服，就是對香菸有所依賴。&lt;/p&gt;
&lt;p&gt;當有「必須要藉由某個人事物來達到目的、受到某個人事物的影響和牽制」時，就是依賴。&lt;/p&gt;
&lt;p&gt;而在程式設計裡面的概念也一樣，如果&lt;strong&gt;Ａ模組直接受到Ｂ模組的影響，我們就稱Ａ依賴了Ｂ&lt;/strong&gt;，最明顯的狀況就是Ａ模組需要藉由Ｂ模組的實例來完成某個功能的時候。&lt;/p&gt;
&lt;p&gt;例如「匯出報表」功能建立了一個「Excel 控制類別」的實例以建立檔案；&lt;br/&gt;
或是「會員查詢」功能建立了一個「DB 連線」的實例來進入資料庫取得會員資料&lt;/p&gt;
&lt;p&gt;遇見這種「必須要藉由某個模組的實例來完成想要的動作」的狀況時，就是依賴。&lt;/p&gt;
&lt;h2 id=&#34;依賴與耦合&#34;&gt;依賴與耦合&lt;/h2&gt;
&lt;p&gt;我們在之前的 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;耦合篇&lt;/a&gt; 提過，如果模組和另一個模組之間有關連，那這兩者之間就耦合。以此來看，依賴就是一種耦合的關係，那麼，依賴是健康還是不健康的耦合呢？&lt;/p&gt;
&lt;p&gt;現在讓我們用 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;多型篇&lt;/a&gt; 用過的「老闆徵工程師」的例子來舉例一下：現在有間小小公司，老闆請來了小明當工程師，並請他開工撰寫產品程式碼。&lt;/p&gt;
&lt;p&gt;當「撰寫產品程式」對「工程師」直接依賴的時候，狀況可能是這樣的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Ming programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;過一陣子，老闆發現小明寫出來的東西似乎不太行，於是把小明趕走，另外請了小華。這時候因為「工程師」這個實作類別不一樣了，我們就必須要改一次程式碼：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Hua programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Hua();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;又過了好一陣子，老闆又另外請了小美來工作。於是又要再改一次，而且小美的工作方式甚至不叫做 &lt;code&gt;Programming&lt;/code&gt;，而是 &lt;code&gt;Coding&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Mei programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Mei();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Coding();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;現在有感覺到一點問題了嗎？如果一直換人，&lt;code&gt;Work&lt;/code&gt; 的程式碼豈不是每次都要修改？甚至根據依賴對象的不同，連使用方式都可能受到影響，很明顯這樣就是所謂不健康的耦合。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這邊還有個例子我很喜歡，在這篇 &lt;a href=&#34;https://notfalse.net/1/dip&#34;&gt;依賴倒置原則&lt;/a&gt; 的文章中，用吃東西來舉例：如果寫死了依賴漢堡，難道一輩子就只能吃漢堡了嗎？如果想改成吃義大利麵，就要修改程式碼；有一百種食物，難道就要改一百次嗎？&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;試想，因為「DB 的連線方法」有了一些變更，使用到該方法的「會員查詢」就連帶要更動，甚至有關聯的地方都必須要變動，如此一來改動的範圍如森林大火般延燒。&lt;/p&gt;
&lt;p&gt;同時由於我們在開發功能的時候，都是讓大功能（高階模組）調用各個小功能（低階模組）來實現目標，越高層的就越整體、越抽象、越接近目標；而越低階就越細節、越接近實作，關注點越小。而我們的思維通常是由大範圍往下到小實作，從整體目標逐漸拆解成各個步驟。&lt;/p&gt;
&lt;p&gt;但是，當我們的高階模組直接依賴低階模組的時候，事情就會變得有點怪怪的。就像董事長必須清潔廁所導致沒空進行公司決策一樣，&lt;strong&gt;原本職責在於高層次、整體的模組，卻不得不因為這些低階模組的變動受到影響&lt;/strong&gt;。那麼隨著層次越高，底下依賴的模組越多，改動的頻率就會提高。&lt;/p&gt;
&lt;p&gt;既然改動範圍又大，改動頻率又高，耦合又不健康，就代表這樣的依賴是有問題的。然而，物件導向的精神就在於讓物件之間互相協作，消除多餘的重複。因此，依賴又是不可能消除的。&lt;/p&gt;
&lt;h2 id=&#34;依賴反轉原則-dependency-inversion-principle&#34;&gt;依賴反轉原則 (Dependency-Inversion Principle)&lt;/h2&gt;
&lt;p&gt;面對這樣的困境，依賴反轉原則告訴我們：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;高階模組不應該依賴於低階模組。兩者都應該依賴抽象。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;是的，不應該直接去依賴，而是必須藉由抽象來隔開。不應該直接去受到實作的影響，而是只要關注在所需要的功能。&lt;/p&gt;
&lt;p&gt;這部分其實已經破梗完了，我們在 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面篇&lt;/a&gt; 已經說明過依賴反轉最基本的思維路線。我們並不是用低階模組的功能直接拼湊出高階模組，讓高階模組直接依賴低階模組然後受到影響；而是把關注點放在需要的功能上，用介面隔開實作，解開他們彼此之間的耦合，介面就是模組之間的抽象層。&lt;/p&gt;
&lt;p&gt;同時也要明白一件重要的事：&lt;strong&gt;並不是高階模組去依賴低階模組。而是高階模組提出它需要的功能，低階模組去實作出這些功能、達成高階模組的目標&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我們並不是因為有「DB 的連線方法」和「處理會員資料的方法」所以才說「我們有這兩個東西欸，那我們來組成會員查詢功能吧」；而是「我們想做一個會員查詢功能，所以我們需要連線到 DB，然後對這些資料做篩選和處理」&lt;/p&gt;
&lt;p&gt;就如同我們在介面的例子所提的一樣：「老闆為了製造產品（高階模組的目標），開出了工程師的應徵條件（介面），而小明前來應徵（低階模組的實作）」&lt;/p&gt;
&lt;p&gt;如此一來，依賴就「反轉」了。&lt;/p&gt;
&lt;p&gt;原本是 &lt;code&gt;高階模組 → 低階模組&lt;/code&gt; 的關係，變成了 &lt;code&gt;高階模組 → 介面 ← 低階模組&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;並不是高階模組去依賴低階模組，而是低階模組去依賴高階模組所要求的功能。&lt;/p&gt;
&lt;p&gt;這也就是依賴反轉原則的第二點：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;抽象不應該依賴細節；細節應該依賴抽象。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;到這邊我們就推完 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面篇&lt;/a&gt; 的前提了，請大家再回顧一下介面篇的內容。也就是說，上面的例子改用抽象層隔離之後，就會和介面篇的例子相同，變成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IProgrammer programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = programmer.Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊就會遇到我們介面篇結束時所問的問題：我們使用功能之前，必須先建立該類別的實例，也就是 &lt;code&gt;new Ming()&lt;/code&gt;，那麼，我們不就還是直接依賴了實作嗎？&lt;/p&gt;
&lt;h2 id=&#34;控制反轉-inversion-of-control-ioc--依賴注入-dependency-injection&#34;&gt;控制反轉 (Inversion of Control, IoC) &amp;amp;&lt;br/&gt; 依賴注入 (Dependency Injection)&lt;/h2&gt;
&lt;p&gt;即使我們反轉了依賴關係，但總是要建立實例才能使用的呀。所以，只是將對具體的依賴更改為對抽象的依賴，仍然是不夠的，在要使用的瞬間就會遭遇到問題。面對這個問題，大大們提出了許多個解決的方法，今天就介紹一個比較常見的方向：&lt;strong&gt;控制反轉 (Inversion of Control, IoC)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;思路非常的簡單：既然如此，我們把實例的建立和實例的使用切分開來就好了，&lt;strong&gt;不再是由高階模組去建立並控制低階模組，而是我們讓一個控制反轉中心去建立低階模組，然後高階模組要使用的時候再把這個低階模組交給高階模組使用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如此一來，控制權也跟著反轉過來了，高階模組從&lt;strong&gt;主動&lt;/strong&gt;建立低階模組，變成&lt;strong&gt;被動&lt;/strong&gt;接收低階模組；&lt;/p&gt;
&lt;p&gt;也就是從原先的：&lt;code&gt;高階模組 —(建立)→ 低階模組&lt;/code&gt;&lt;br/&gt;變成了：&lt;code&gt;高階模組 ←(傳遞低階模組)— 控制反轉中心&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;控制反轉的概念比較像是：當肚子餓的時候，如果自己煮菜的時候，必須自己備料、自己烹調、才能有東西吃。但如果去餐廳點餐，只要說出自己想要的餐點，店家就會負責備料，廚師就會烹調，最後就把需要的餐點送上桌來吃。&lt;/p&gt;
&lt;p&gt;也就是說，&lt;strong&gt;高階模組再也不需要關心如何建立，該建立哪個實體，只專注於使用功能，真正達到介面的精神。低階模組也只需要等待控制反轉中心分發，到了崗位就把份內事做好，專心在自己的職責身上即可&lt;/strong&gt;。如此一來就能解除兩者之間的耦合。&lt;/p&gt;
&lt;p&gt;但是，要怎麼把控制中心建立的低階模組，交給高階模組做使用呢？這時候的實作方式就是我們所謂的 &lt;strong&gt;依賴注入 (Dependency Injection)&lt;/strong&gt; 了。&lt;/p&gt;
&lt;p&gt;依賴注入說穿了很簡單，就是&lt;strong&gt;用各種姿勢把東西丟進去給類別使用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如說我們先前提過的 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;建構式&lt;/a&gt;，就是其中一種解決方法。用上面的例子，就會變成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ProductService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; IProgrammer _programmer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; ProductService(IProgrammer programmer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._programmer = programmer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Product Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; product = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._programmer.Programming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; product;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在這個例子中，我們利用建構式的方式，從外部傳入該介面的實體來使用。現在撰寫產品程式碼的工作再也不用為了換工程師而改變，也不用因為實作細節或是方法名稱而煩惱，只要照個介面合約使用就可以了。至於要傳遞哪個實體進來，這份工作要交給小明還是小美，就讓控制中心去決定，大家各司其職，落實單一職責。&lt;/p&gt;
&lt;p&gt;當然，注入的方式不只建構式注入，還有設值注入（也就是從外部改變目標的某個屬性值來達到注入）等等；提供 IOC 的方式也不只一種，例如 .net 的 Unity，甚至到了 .net Core 時代 IOC 還直接是內建的功能呢，由於口味眾多，此處暫且按下不表。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：下個系列文補了 &lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;，有興趣的朋友可以接續看看&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;結語&#34;&gt;結語&lt;/h2&gt;
&lt;p&gt;那麼，我們最後再來複習一遍：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;高階模組不應該依賴於低階模組，兩者都應該依賴抽象&lt;/strong&gt;。為了解除耦合，必須用介面這種抽象層進行隔離。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;抽象不應該依賴細節。細節應該依賴抽象&lt;/strong&gt;。介面應該是高階模組提出的要求，然後才去使用實作了這些要求的低階模組。這些實作應該圍繞著這些要求，而不是讓要求去配合實作，更不要讓要求中包含實作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;為了解決介面實例化仍然會產生依賴的問題，就有了控制反轉&lt;/strong&gt;。把控制權交給第三方，藉此讓使用者能夠不用關心實例化的過程，而注重在使用並達成目標的職責上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;而控制反轉的具體實現方法是依賴注入&lt;/strong&gt;，藉由從建構式傳遞、更改目標的屬性等方式，把低階模組交給高階模組使用者。當我們藉由依賴注入的方式實現控制反轉，就能夠讓物件的設計符合依賴反轉原則。&lt;/p&gt;
&lt;p&gt;這個部份的做法還是挺複雜的，&lt;del&gt;所以才拖稿這麼久&lt;/del&gt;，因此決定把原因的順序推過一遍，也算是幫自己重新了解一次。參考資料有蠻多篇我都相當喜歡，想更了解依賴反轉、控制反轉等等的朋友可以再自行閱讀。那麼，我們下次見～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://notfalse.net/1/dip&#34;&gt;依賴倒置原則 (Dependency-Inversion Principle, DIP) - NotFalse 技術客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://notfalse.net/3/ioc-di&#34;&gt;控制反轉 (IoC) 與 依賴注入 (DI) - NotFalse 技術客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@f40507777/%E4%BE%9D%E8%B3%B4%E5%8F%8D%E8%BD%89%E5%8E%9F%E5%89%87-dependency-inversion-principle-dip-bc0ba2e3a388&#34;&gt;依賴反轉原則 Dependency Inversion Principle (DIP) - Finn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10191603&#34;&gt;從被動變主動—依賴反轉 - 伊恩 - iT邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dustinhsiao21.github.io/dp/solid-dependency-inversion-principle/&#34;&gt;SOLID 原則 - Dependency Inversion Principle(依賴反轉原則) - Dustin;s murmur&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-6_dependency-inversion-principledip-%E4%BE%9D%E8%B3%B4%E5%8F%8D%E8%BD%89%E5%8E%9F%E5%89%87/&#34;&gt;Object Oriented物件導向設計原則SOLID-6:Dependency Inversion Principle(DIP)依賴反轉原則 - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5-didependency-injection/&#34;&gt;依賴注入 DI(Dependency Injection) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2018/07/30/144329&#34;&gt;[小菜一碟] 談談物件導向設計原則中 DIP（依賴反轉原則）中的 Dependency（依賴） - 軟體主廚的程式料理廚房&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jyt0532.com/2020/03/24/dip/&#34;&gt;深入淺出依賴反向原則 Dependency Inversion Principle - jyt0532&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/daniel/2018/01/17/140435&#34;&gt;IOC(控制反轉)，DI(依賴注入) 深入淺出~~ - 石頭的coding之路&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>7&#43; Taskbar Tweaker —— 簡單方便的 Windows 工作列調整工具</title>
      <link>https://igouist.github.io/post/2020/12/7_taskbar/</link>
      <pubDate>Sun, 06 Dec 2020 23:49:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/12/7_taskbar/</guid>
      <description>&lt;p&gt;故事是這樣的——&lt;/p&gt;
&lt;p&gt;Win10 工作列的合併設定有這些選項：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yZypwQD.webp&#34;
  alt=&#34;&#34;width=&#34;480&#34; height=&#34;166&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當選擇「一律、隱藏標籤」時，工作列上同樣的程式就會摺疊起來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8JvOmkJ.webp&#34;
  alt=&#34;&#34;width=&#34;145&#34; height=&#34;49&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而「永不」和「當工作列滿時」則會將工作列展開：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BqA2ERo.webp&#34;
  alt=&#34;&#34;width=&#34;550&#34; height=&#34;40&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;好的，那麼像我個性這麼麻煩的人，如果覺得顯示名字很佔位置，可是又不想要摺疊之後按兩次才能打開我要的應用程式，偏偏又很愛開一整排 IDE 的話，有沒有什麼簡單的辦法&lt;strong&gt;不要讓圖示合併，但也不要顯示名字呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果有個小工具，可以讓這些工作列的設定更彈性就好了，會有嗎？&lt;/p&gt;
&lt;p&gt;有的！&lt;/p&gt;
&lt;p&gt;今天要記錄的是 &lt;strong&gt;7+ Taskbar Tweaker&lt;/strong&gt; 這個小東東，他的畫面是長這樣的&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bSDYzFO.webp&#34;
  alt=&#34;&#34;width=&#34;693&#34; height=&#34;522&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以調整的部分挺多的，可以隨個人喜好進行調整，從滑鼠操作工作列一路到時間要不要顯示秒都有。&lt;/p&gt;
&lt;p&gt;例如我前面想要的需求，就可以把分組的部分改成「&lt;strong&gt;不分組&lt;/strong&gt;」&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UePSYB9.webp&#34;
  alt=&#34;&#34;width=&#34;224&#34; height=&#34;158&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣工作列的圖示就變成：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XJHECgm.webp&#34;
  alt=&#34;&#34;width=&#34;227&#34; height=&#34;40&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;就是這樣！&lt;/p&gt;
&lt;p&gt;另外如果對這些選項仍然不滿足的朋友，也可以右鍵開啟&lt;strong&gt;進階選項&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TrJ7IhJ.webp&#34;
  alt=&#34;&#34;width=&#34;227&#34; height=&#34;221&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;像我就有開啟大 icon，看了就是比較舒服&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/oTshGL4.webp&#34;
  alt=&#34;&#34;width=&#34;413&#34; height=&#34;79&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;另外也能自訂一些滑鼠操作、鍵盤快速鍵等等，能微調的地方蠻多的，很適合有興趣的朋友可以自己研究看看。另外也要感謝巴哈的這篇 &lt;a href=&#34;https://forum.gamer.com.tw/C.php?bsn=60030&amp;amp;snA=525114&#34;&gt;徹底爆改&lt;/a&gt; 多挖了很多小玩具可以玩，哈。&lt;/p&gt;
&lt;p&gt;那麼今天就介紹到這裡，絕對不是玩小工具和逛耶誕城結果就廢掉沒寫文囧，抱歉啦催稿的碰油，我們下次見～&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>讀《離開公司，我過得還不錯》</title>
      <link>https://igouist.github.io/post/2020/11/i-am-doing-well-after-leaving-the-company/</link>
      <pubDate>Sun, 29 Nov 2020 23:54:06 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/11/i-am-doing-well-after-leaving-the-company/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8QX3RPd.webp&#34;
  alt=&#34;&#34;width=&#34;353&#34; height=&#34;501&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;無論喜不喜歡自己的工作，都應該好好思考自己和工作的關係，畢竟每天花三分之一的時間做這件事，如果和它處得不好，人生也不可能會變好。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這本書並不是那種精神勝利法，又或是超猛接案全攻略之類的書；反而給我的感覺比較像是作者藉著這本書，分享他這一路摸索的心得，和遭遇到的一些困難，看的時候有種像在和作者聊聊天的感覺。&lt;/p&gt;
&lt;p&gt;也因為這本書比較接近作者分享他選擇的道路、內容比較廣雜，遇到的部分都稍微說一些，但不是那種針對某個議題深入探討的書籍，所以比較適合想稍微了解自由工作的朋友閱讀。同時也必須了解到，自由工作者也只是眾多職業道路的其中一種，每個人的條件也不盡相同，因此抱持著好奇的心情閱讀，會更有收穫的感覺。&lt;/p&gt;
&lt;p&gt;從 &lt;a href=&#34;https://www.books.com.tw/products/0010810277&#34;&gt;博客來&lt;/a&gt; 的書籍簡介就可以看到，目錄真的把自由工作遇到的議題都碰了一些：從適不適合接案工作，到報稅、勞保、合約、工作場所都聊了一點。因為真的挺廣的，這邊我就只記錄一些我比較感興趣的話題。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#我們和工作之間的關係&#34;&gt;我們和工作之間的關係&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#自由與不自由&#34;&gt;自由與不自由&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#重新定義&#34;&gt;重新定義&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#延伸閱讀&#34;&gt;延伸閱讀&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;我們和工作之間的關係&#34;&gt;我們和工作之間的關係&lt;/h2&gt;
&lt;p&gt;本書最開始的時候作者敘述了他踏入自由工作的歷程：太過熱情工作、燃燒殆盡、失去熱情、休養了一年，最後重新思考人與工作之間的關係，才決定踏上接案的道路。&lt;/p&gt;
&lt;p&gt;就像我這篇文章決定引用作者前輩說過的話作為開場，我也相當認同：&lt;strong&gt;不管你做什麼，工作在大多數人的生命中都佔了不少的份量&lt;/strong&gt;。但我們真的有好好思考過我們和工作之間的關係嗎？有思考我們為什麼工作、想要什麼工作、適合什麼工作嗎？只要曾經思考過這些問題，就會明白我們和工作之間的關係，其實就是我們和生活之間的關係。而我們想要的工作，就是我們想要的生活。&lt;/p&gt;
&lt;p&gt;當作者的前言問說：「總是人去符合職位的要求，把我們原本奇怪的形狀塞進方方正正的外框，身處職場當然不可能完全舒服，但，有沒有可能換個角度，從自己原本奇怪的形狀延伸，慢慢長出各種不同的工作」這倒讓我好像看到了 &lt;a href=&#34;https://igouist.github.io/post/2020/06/darkhorse&#34;&gt;黑馬思維&lt;/a&gt; 的影子，黑馬思維也是要求我們要先了解自己的微動力，再根據自身去選擇策略。&lt;/p&gt;
&lt;p&gt;因此，當作者接著說明上班的心態和接案的心態：上班打混過了一天也是一天的薪水，但接案為了生存就會講究效率；但上班相對比較穩定，有安心感，接案則是有做有錢沒做沒錢，兩者之間對於效率和時間的意義相當不同。從這開始，其實也就區分了適合接案和適合上班的人們了。&lt;/p&gt;
&lt;p&gt;書中也有了一小段來接續這個話題，說明作者認為誰適合上班，誰又適合自由工作。上班相對於自由工作，有三個明顯的優勢：明確的社會位置、穩定的收入來源、勞基法保障的福利。因此，不確定想做什麼的朋友，或是喜歡穩定、安全的朋友就比較適合上班。&lt;/p&gt;
&lt;p&gt;反過來說，自由工作者必須了解自己在社會中的位置，用專業來取代職稱，在開始接案的時候，也得要先找一件有趣的事情開始做，其他任務才會找上門來。也因此最好有點存款，才能保留接案時的選擇權等等。所以比較適合能承受不穩定性，又或是有想持續追求的目標的人。&lt;/p&gt;
&lt;p&gt;要注意，這邊的承受不穩定性包含的心理和經濟能力等方面。例如作者的家人能夠經濟獨立，並且作者對工作的要求是能夠賺取生活所需的金錢讓他能夠自由做想做的事情即可，因此在衡量接案和上班之間的差異時，千萬別忘了要從自身的條件、想要的生活等方面開始估算。試著去想想兩者之間的條件，和自己比較傾向哪邊的生活。&lt;/p&gt;
&lt;p&gt;即使最後發現此路不可行，但就像書中說的：「&lt;strong&gt;光是從不同角度去思考工作和自己的關係，換個視野看看職場十字路口的風景，也都會有收穫。&lt;/strong&gt;」&lt;/p&gt;
&lt;h2 id=&#34;自由與不自由&#34;&gt;自由與不自由&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;自由意味著責任，正因為如此，多數人都懼怕自由。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;都說自由接案比較自由，但聽來聽去也不知道到底有多自由，曾經聽到唉呀那個接案的又出國玩啦，也曾聽到欸那個誰案子趕不完了好像三天沒睡了，總感覺這族群對我來說有點…太過動盪？所以「接案有多自由？」一直是我蠻有興趣的問題。&lt;/p&gt;
&lt;p&gt;作者在書中也列出了幾項接案工作者的自由和不自由，例如能夠隨心所欲安排時間、能夠自己選擇案子、可能自己調整工作內容等等。同時也會有偷懶就沒有收入，必須維持工作成果等壓力。&lt;/p&gt;
&lt;p&gt;但就像本節引用的這句「&lt;strong&gt;自由意味著責任&lt;/strong&gt;」，為了能夠隨心所欲安排時間，必須先學會時間規劃，必須要能夠遵守紀律；為了能夠自己選擇案子，必須要維持住經濟水準，也必須要磨練專案到擁有話語權；為了能夠自己調整工作內容，就必須好好思考過工作和生活的比例，必須自己找到目標。所有的自由相對都需要先有所付出，因此自由並不是比較簡單的道路，而是學會對自己負責，還有可能餓死。&lt;/p&gt;
&lt;p&gt;也因為這些責任，所以自由工作者必須具備一些能力，大多數作者都會在書中提到，例如：&lt;/p&gt;
&lt;p&gt;在財務管理方面，就不能像領薪水的上班族一樣，每個月五號十號無腦等錢入帳，必須主動按照專案去管理每一筆帳。&lt;/p&gt;
&lt;p&gt;而且&lt;strong&gt;通常是同時多個專案非同步地進行著&lt;/strong&gt;，例如Ａ專案做到一半Ｂ專案開始，同時手上還有Ｃ專案準備啟動會議等等，這些專案簽訂的合約、入賬的時機也都有可能不同。&lt;/p&gt;
&lt;p&gt;所以就必須學會掌握各個專案的進度和帳務狀況等等，所以自由接案者只能被迫好好學記帳，才能掌握住工作和款項的節奏，避免周轉不靈或是破產。&lt;/p&gt;
&lt;p&gt;同時，既然都有可能多個專案非同步地進行了，為了能夠自由地工作和生活，也就必須好好把工作狀況記錄下來，回顧和規畫整體的時間。&lt;/p&gt;
&lt;p&gt;就像公司前輩曾經說過的：&lt;strong&gt;能夠估算工時，也是工程師專業中的一環&lt;/strong&gt;。那麼保持對時間的敏感度，把主導權拉回自己身上，自然也是自由工作者的一項專業與責任吧。&lt;/p&gt;
&lt;p&gt;針對時間管理，作者認為應該從記錄開始，並提出了四個步驟：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每天記錄做了什麼&lt;/li&gt;
&lt;li&gt;每周歸納整體工作&lt;/li&gt;
&lt;li&gt;每月回顧時間使用狀況&lt;/li&gt;
&lt;li&gt;每年檢討新目標&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;說實在的，我看到這段不自主驚呼，這不是子彈筆記的精神嗎？因此這邊就不再贅言，直接丟幾篇對我影響比較大的時間管理文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2015/07/calendar-project.html&#34;&gt;實作進攻型行事曆：學會留時間給自己的進度排程心法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/how-gipi-learn/iterate-your-week-schedule-81e1aa8843b2&#34;&gt;把週行事曆當專案管，實現每週自我迭代&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://readingoutpost.com/the-bullet-journal-method/&#34;&gt;看膩了繽紛的子彈筆記？手殘派科技人的6個月實踐心得&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;既然前面都計算了賺錢的效率，在選擇案子的時候，自然就會開始有所取捨。就如同書中所說的：什麼案子都接的話，跟在公司上班有什麼兩樣？&lt;/p&gt;
&lt;p&gt;因此，就會劃分出賺錢與不賺錢的案子、有興趣和沒興趣的案子，甚至感覺不太對勁的案子等等。&lt;/p&gt;
&lt;p&gt;在書中，作者的工作收入要求只要能維持生活即可，因此他會主動搭配賺錢的案子和比較有興趣的案子，讓自己邊維持生活又能有時間做有興趣的工作。&lt;/p&gt;
&lt;p&gt;關於案件的選擇和搭配這部份，畢竟比較吃個人經驗，我就沒什麼特別的心得。但書中有個建議仍然值得參考：&lt;strong&gt;請相信直覺&lt;/strong&gt;，當直覺有問題，那通常就真的有問題。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2kTqFln.webp&#34;
  alt=&#34;&#34;width=&#34;1149&#34; height=&#34;718&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當你感覺到危，猶豫就會敗北。&lt;/p&gt;
&lt;p&gt;除了上面這三項以外，還有許多篇幅用來說明 工作場所、報價與合約等等，過於實際操作的部份，這邊就略過不提了。&lt;/p&gt;
&lt;h2 id=&#34;重新定義&#34;&gt;重新定義&lt;/h2&gt;
&lt;p&gt;書中最後的篇幅，作者用自己的看法重新定義了生活、職涯、退休、成功等等話題。&lt;/p&gt;
&lt;p&gt;其中有幾個部分我挺有興趣的，例如作者說：「在討論斜槓、自由工作時，有幾個前提條件得說清楚：只是兼差很多，卻沒有自己的熱情與專長，不能算斜槓；如果接一堆案子，卻沒有挑案子的自由、沒有安排時間的原則、說不出對工作和生活的想法，也稱不上是自由工作者。」&lt;/p&gt;
&lt;p&gt;無情的接案機器，似乎也跟爆肝上班族沒什麼兩樣，都一樣沒有自由。接著作者又問：能否不依賴公司，以個人名義在職場生存？&lt;/p&gt;
&lt;p&gt;從這些出發點思考，到「&lt;strong&gt;拿回人生的選擇權&lt;/strong&gt;」就是作者對自由工作者的「自由」下的定義。&lt;/p&gt;
&lt;p&gt;想要自由，也得先從思考生活和培養熱情、磨練專長開始。&lt;/p&gt;
&lt;p&gt;不過這就像是書中提到的：「自由工作者憑什麼生存？小說家尼爾蓋曼（Neil Richard Gaiman）說，&lt;strong&gt;才華洋溢、交件準時、人好相處，是三項重要能力&lt;/strong&gt;」並且，只要能符合其中兩項就足以好好生存了。&lt;/p&gt;
&lt;p&gt;但這些能力，和上面能夠自由的條件，不管放到自由工作者或是職場，我個人覺得都是適用的，因為這些就是個人的軟實力。所以，不論你選擇上班工作，或是自由接案，終究還是要走向自我成長。&lt;/p&gt;
&lt;p&gt;而成長的方向和道路何其多，又怎麼不讓人好好思考、好好摸索呢？&lt;/p&gt;
&lt;p&gt;最後謝謝這本書，算是讓我思考了一個新的方向。書中其實還介紹了蠻多作者的心得和自由工作的一堆眉眉角角，也推薦有興趣的朋友可以讀看看，當作了解自由工作的入門磚，多多少少也會有收穫的吧。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這本書，描述的就是我的嘗試方法、犯錯教訓，還有交換的代價。不過我摸索出來的道路，也只是解開工作謎團的其中一條路線而已，每個人都可以有自己的路線，而且比我的更寬廣自在才對。&lt;/p&gt;
&lt;p&gt;《離開公司，我過得還不錯：成為自由工作者的理想生活提案》&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;延伸閱讀&#34;&gt;延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.thenewslens.com/article/112375&#34;&gt;《離開公司，我過得還不錯》：能否不依賴公司，以個人名義在職場生存？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://imjanet.com/freelancer/&#34;&gt;閱讀心得｜離開公司，我過得還不錯 成為自由工作者的理想生活提案 劉揚銘 - JJ&amp;rsquo;S TRAVEL &amp;amp; LIFESTYLE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/unique-reader/%E9%9B%A2%E9%96%8B%E5%85%AC%E5%8F%B8-%E6%88%91%E9%81%8E%E5%BE%97%E9%82%84%E4%B8%8D%E9%8C%AF-b92a2bae0ac9&#34;&gt;離開公司，我過得還不錯！ - 優‧悅讀&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.books.com.tw/products/0010825335&#34;&gt;一人公司：為什麼小而美是未來企業發展的趨勢&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞抓蟲: 在 Amazon Linux AMI 安裝 .Net Core 時卡在 Requires: openssl-libs</title>
      <link>https://igouist.github.io/post/2020/11/bugs-install-dotnet-core-on-amazon-ami-requires-openssl/</link>
      <pubDate>Sun, 22 Nov 2020 23:40:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/11/bugs-install-dotnet-core-on-amazon-ami-requires-openssl/</guid>
      <description>&lt;p&gt;最近遇到在 Amazon Linux AMI 要安裝 .net Core 3.1 環境的時候，會一直跳出&lt;br/&gt; &lt;code&gt;Requires: openssl-libs&lt;/code&gt; 而無法安裝的問題，儘管明明已經有 openssl 了，但還是解析失敗找不到依賴，過程一直碰壁，因此在這邊紀錄一下。&lt;/p&gt;
&lt;p&gt;過程中嘗試了安裝 openssl-libs（會找不到該套件）、下載 Dotnet 的 tar.gz ，再直接對執行檔下 Dotnet 指令起站台（雖然網站起得來，但執行者會是當下的登入身分，也就是 &amp;lsquo;&amp;rsquo;@連線進來的IP-伺服器位置，而非由本機執行。後續如果有連線資料庫等檢查權限的地方就很容易出錯）&lt;/p&gt;
&lt;p&gt;最後在 Dotnet Core 的 issue 翻到這篇 &lt;a href=&#34;https://github.com/dotnet/core/issues/930&#34;&gt;Cannot install .NET Core 2.0 on Amazon Linux AMI&lt;/a&gt; 才成功解決。&lt;/p&gt;
&lt;p&gt;首先先將 openssl-libs 的 SPEC 抓下來，然後給 RPM 建置一下。這兩句可以參考一下這篇 &lt;a href=&#34;https://medium.com/linux-%E9%96%8B%E7%99%BC%E5%85%A5%E9%96%80/rpm-%E6%89%93%E5%8C%85-%E7%94%B1%E4%B8%80%E7%AB%85%E4%B8%8D%E9%80%9A%E5%88%B0%E5%8B%95%E6%89%8B%E6%BF%AB%E7%94%A8-%E4%BA%8C-df9eea70bd7b&#34;&gt;RPM 打包︰由一竅不通到動手濫用 (二)&lt;/a&gt; 的說明。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget https://github.com/dotnet/core/files/2186067/openssl-libs-ami.spec.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rpmbuild --bb openssl-libs-ami.spec.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們去把打包好的 openssl-libs 安裝起來，路徑可能會有不同，所以記得先用 find 找一下&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo find / -iname RPMS
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo rpm -i /usr/src/rpm/RPMS/x86_64/openssl-libs-1.0.0-0.x86_64.rpm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到這邊應該就解決標題遇到的問題囉！讓我們回到安裝的正軌&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum install -y powershell
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum install dotnet-sdk-3.1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後就可以 &lt;code&gt;dotnet&lt;/code&gt; 試試囉！&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (13): 介面隔離原則</title>
      <link>https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle/</link>
      <pubDate>Sun, 15 Nov 2020 12:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/itHN6VZ.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#介面隔離原則interface-segregation-principle&#34;&gt;介面隔離原則（Interface Segregation Principle）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#介面也要單一職責&#34;&gt;介面也要單一職責&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#用組合實現功能&#34;&gt;用組合實現功能&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#後日談&#34;&gt;後日談&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#同系列文章&#34;&gt;同系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;p&gt;今天要記錄的是介面隔離原則，顧名思義是和介面高度相關的原則。因此在閱讀本篇之前，可能需要先對 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt; 有一點了解呦。&lt;/p&gt;
&lt;p&gt;事情就從上一篇 &lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;里氏替換原則&lt;/a&gt; 的鳥類物流公司開始說起。老闆痛定思痛，決定先用&lt;strong&gt;介面&lt;/strong&gt;先規定好物流士們的應徵條件，例如裝貨、卸貨、飛行、必須有帥氣的喙等等。&lt;/p&gt;
&lt;p&gt;這道命令下來後，倉庫們的企鵝都慌了，來檢查的編譯器瘋狂跳出 Error:「您未實作 IBird 的 Fly() 方法！」這下怎麼辦呢，為了要保住飯碗，企鵝們就必須實作出飛行才行，可是企鵝真的就不會飛呀！&lt;/p&gt;
&lt;p&gt;這下子企鵝們只剩下兩個選擇：不實作飛行，但是就不能被當成物流士，最後就會被開除；或是……空實作。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Penguin&lt;/span&gt; : IBird
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Fly()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// Do nothing;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;企鵝們終於騙過了編譯器檢查員，然而當送貨的命令下來之後，企鵝們再一次卡在倉庫門口發呆，最終物流公司仍然踏上了虧損的老路，再度面臨倒閉危機…&lt;/p&gt;
&lt;h2 id=&#34;介面隔離原則interface-segregation-principle&#34;&gt;介面隔離原則（Interface Segregation Principle）&lt;/h2&gt;
&lt;p&gt;不知道大家對企鵝遇到的狀況有沒有經驗呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;當介面規定了太多要求，而我們實作的子類別只需要其中一部份，或是有些要求根本無法達成，就會發生這個困境：放棄實作介面，或是用空實作和錯誤處理去欺騙介面&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如資料庫存取的介面要求太多和當下資料庫過於一致的方法，結果替換資料庫的時候導致部份方法實作不出來；或是像 &lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-4_interface-segregation-principleisp-%E4%BB%8B%E9%9D%A2%E9%9A%94%E9%9B%A2%E5%8E%9F%E5%89%87/&#34;&gt;俺同事文章&lt;/a&gt; 中的例子，交通工具的介面要求能開關車門，結果電動機車無法實作。&lt;/p&gt;
&lt;p&gt;然而，如果我們選擇用空實作或是拋出錯誤的方式，去欺騙介面，等到需要呼叫該方法的時候，就會發生許多非預期的錯誤。甚至讓接手程式碼的人在什麼都不知道的情況之下就讓系統掛掉。聰明的朋友們一定發現了，這就是違反了 &lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;里氏替換原則&lt;/a&gt;！&lt;/p&gt;
&lt;p&gt;為了迴避到處都是空實作地雷的結局，大前輩們就提出了介面隔離原則：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;不應該強迫用戶依賴它們未使用的方法&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這邊的用戶也就是我們的子類別，它們等同是這個介面的使用者。當我們必須強迫使用者去實作一些他們不需要的方法時，就代表了一個事實：我們的介面太「胖」了！裡面的某些要求可能是非必要的，以至於造成了實作上的冗餘。&lt;/p&gt;
&lt;p&gt;也基於這條原則延伸出了一個方向：&lt;strong&gt;應該最小化類別與類別之間的介面&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;介面也要單一職責&#34;&gt;介面也要單一職責&lt;/h2&gt;
&lt;p&gt;但是，我們要怎麼知道是我們設計的介面太胖，還是子類別在偷懶呢？又要怎麼知道我們的介面設計是否已經「最小化」呢？&lt;/p&gt;
&lt;p&gt;那就是 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;單一職責原則&lt;/a&gt; 出場的時候了。一個合理的介面設計是能夠符合單一職責原則的，反過來說，我們可以用單一職責原則來檢視我們的介面設計是否良好。&lt;/p&gt;
&lt;p&gt;當我們設計介面的時候，或是像上面遇到必須空實作的時候，就可以思考一下：&lt;strong&gt;這個介面的職責是否單一？這個介面的意圖是什麼？這個介面是否只對一個角色負責、只有一個原因改變？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;當我們的介面符合單一職責、足夠 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;內聚&lt;/a&gt; 的時候，我們自然就能夠說這個介面已經足夠精簡了。&lt;/p&gt;
&lt;h2 id=&#34;用組合實現功能&#34;&gt;用組合實現功能&lt;/h2&gt;
&lt;p&gt;有些人可能就會疑惑了：「但是我就是需要這個功能呀，如果我不塞在介面，要放去哪呢？」&lt;/p&gt;
&lt;p&gt;很簡單，&lt;strong&gt;放去另一個該職責的介面就可以了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;和繼承需要注意的部分一樣，濫用介面也是濫用繼承，我們應該用&lt;strong&gt;組合&lt;/strong&gt;去實現功能而不是用繼承去綁死功能。一個資料串列能做的功能可能相當多，但我們並不需要一次就要求實現全部能做的事情，而是將這些工作分組，再從中組合出我們需要的部份。&lt;/p&gt;
&lt;p&gt;此處以 C# 來說，例如我們很常接觸的 &lt;code&gt;List&lt;/code&gt; 類別，並不是只實作了 &lt;code&gt;IList&lt;/code&gt;，而是實作了 &lt;code&gt;ICollection&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;IEnumerable&lt;/code&gt;, &lt;code&gt;IList&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;IReadOnlyCollection&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;IReadOnlyList&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;ICollection&lt;/code&gt;, &lt;code&gt;IList&lt;/code&gt; 這些介面，層層堆疊，相互嵌合。&lt;/p&gt;
&lt;p&gt;那我們就知道 List 由這些介面「組合」而成，也知道了我們 List 具有這些介面要求的能力，讓我們能在合適的時候使用這個類別。同時又保留了這些介面能搭配出另一種組合的彈性，例如 &lt;code&gt;Array&lt;/code&gt; 就是由 &lt;code&gt;ICollection&lt;/code&gt;, &lt;code&gt;IEnumerable&lt;/code&gt;, &lt;code&gt;IList&lt;/code&gt;, &lt;code&gt;IStructuralComparable&lt;/code&gt;, &lt;code&gt;IStructuralEquatable&lt;/code&gt;, &lt;code&gt;ICloneable&lt;/code&gt; 組合而成。&lt;/p&gt;
&lt;p&gt;就像可能有位大神，名片一拿出來就是一串「程式設計師／架構師／攝影師／貓奴」，&lt;strong&gt;我們的類別也要懂得斜槓&lt;/strong&gt;。如此一來類別就比較不容易被介面綁死，也能因應不同場合來決定身分，從「每次都被逼著買套餐可是又不喜歡小菜」變成「餐餐自由配」，組合就該如此自由！&lt;/p&gt;
&lt;p&gt;回到開頭的例子，把想要的行為全部定義在一個介面裡，然後用一個類別去實現它遇到不需要的動作就詐騙介面，是相當不 OK 的；而是應該&lt;strong&gt;把想要的行為用職責的角度去思考&lt;/strong&gt;，根據職責建立成一或多個介面。然後只&lt;strong&gt;挑選&lt;/strong&gt;並實作該類別需要的動作（介面），如此就可以讓介面不再臃腫，而是變得靈活。&lt;/p&gt;
&lt;p&gt;就像是武術秘笈中的招式，其實也是一連串的動作所組成；所謂的功能，其實也是一連串行為所組成的。既然行為組合成了功能，我們也要從組合的角度去思考如何建立類別。&lt;strong&gt;組合就像是積木一樣&lt;/strong&gt;，我們用積木堆疊來完成作品，同時每個積木又可以各自靈活運用。&lt;/p&gt;
&lt;p&gt;而積木也分成了好用的積木，和很難使用的積木，在程式中可以從夠不夠 SOILD 看出來。不好用的那些用起來會覺得卡卡的，測試也很難寫；好用的則會讓你面對變化的時候，就像拆裝樂高一樣順手方便。聰明的朋友可能聯想到了，這就是 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;開放封閉原則&lt;/a&gt; 中我們提過的模組化。&lt;/p&gt;
&lt;p&gt;通常來說積木的形狀越複雜、體積越大，就越難以靈活使用，介面也是如此，因此我們在設計介面的時候，要謹記&lt;strong&gt;介面隔離原則&lt;/strong&gt;，利用我們在單一職責原則、里氏替換原則學到的原則來檢驗我們的介面，如此就可以迴避相當多尷尬兩難的實作場面，也能讓介面的使用更加靈活。&lt;/p&gt;
&lt;p&gt;那麼，在結束之前，有興趣的朋友可以跟我一起想一想：介面隔離原則，只適用於設計類別架構時的介面嗎？其他的介面（Interface）呢？例如 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;API&lt;/a&gt;，是不是也可以按照介面隔離原則的精神下去設計呢？&lt;/p&gt;
&lt;h2 id=&#34;後日談&#34;&gt;後日談&lt;/h2&gt;
&lt;p&gt;企鵝詐騙介面的事情終究還是暴露了。&lt;/p&gt;
&lt;p&gt;但是這群企鵝的夢想就是成為物流士，老闆也狠不下心把牠們趕走。&lt;/p&gt;
&lt;p&gt;「也許……」&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E5%B0%8F%E9%BB%84%E9%B8%AD%E8%B0%83%E8%AF%95%E6%B3%95&#34;&gt;鴨子顧問&lt;/a&gt;說：「我們可以有別的方法。只要使用&lt;strong&gt;介面隔離原則&lt;/strong&gt;。」&lt;/p&gt;
&lt;p&gt;老闆：『介面隔離？怎麼做呢？』&lt;/p&gt;
&lt;p&gt;「我們可以把送貨放到 IDelivery，然後讓他們用不同的介面來實作移動方式，例如 IFly、ISwim、IRun 等等。用&lt;strong&gt;組合&lt;/strong&gt;的方式來完成不同種類的物流士類別，這樣就可以有很多種送貨方式了」&lt;/p&gt;
&lt;p&gt;『原來如此，不只是空運 —— 我們要征服陸海空嗎！』&lt;/p&gt;
&lt;p&gt;改變作法的鳥禽物流公司搖身一變成了動物物流公司，同時廣徵天下動物，除了企鵝也能從南極發貨中心快速運貨以外，公司還招到了明星成員獵豹物流士，從此蒸蒸日上、強勢打入各大物流市場，最後進軍宇宙。可喜可賀，可喜可賀。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@f40507777/%E4%BB%8B%E9%9D%A2%E9%9A%94%E9%9B%A2%E5%8E%9F%E5%89%87-interface-segregation-principle-isp-6854c5b3b42c&#34;&gt;介面隔離原則 Interface Segregation Principle (ISP) - Finn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10192464&#34;&gt;SOLID 之 介面隔離原則（Interface segregation principle）- Miles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/hatelove/2010/10/17/interface-segregation-principle&#34;&gt;91 之 ASP.NET 由淺入深 不負責講座 Day20 - ISP 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://daydaynews.cc/zh-tw/technology/346506.html&#34;&gt;設計模式之美十六：介面隔離原則有哪三種應用？介面該如何理解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://yncbearz.github.io/2020/06/03/Interface-Segregation-Principle/&#34;&gt;介面隔離原則 - YNCBearz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-4_interface-segregation-principleisp-%E4%BB%8B%E9%9D%A2%E9%9A%94%E9%9B%A2%E5%8E%9F%E5%89%87/&#34;&gt;Object Oriented物件導向設計原則SOLID-4:Interface Segregation Principle(ISP) 介面隔離原則 - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789864342099&#34;&gt;《無瑕的程式碼：物件導向原則、設計模式與C#實踐》&lt;/a&gt; Ch.12 ISP：介面隔離原則&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Dark Reader —— 暗黑模式愛好者的 Chrome 必備套件</title>
      <link>https://igouist.github.io/post/2020/11/dark-reader/</link>
      <pubDate>Sun, 08 Nov 2020 23:39:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/11/dark-reader/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/G4OjUzE.webp&#34;
  alt=&#34;&#34;width=&#34;650&#34; height=&#34;130&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不能信任那些 Terminal 或編輯器用白底的人。&lt;/br&gt;
—— &lt;a href=&#34;https://github.com/CodeTengu/JokeKappa/blob/master/jokekappa/jokes/codetengu_weekly.json&#34;&gt;JokeKappa&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這禮拜推薦了個常用的 chrome 套件給同樣喜歡黑色背景的同事，這邊也推薦給大家。&lt;/p&gt;
&lt;p&gt;&lt;s&gt;絕對不是因為隻狼更新了不小心砍太爽，結果來不及寫介面隔離只能介紹套件水一下，Heiya～&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;今天要介紹的就是這款 &lt;a href=&#34;https://chrome.google.com/webstore/detail/dark-reader/eimadpbcbfnmbkopoojfekhnkhdbieeh&#34;&gt;Dark Reader&lt;/a&gt;，這是我用 chrome 時首選的暗黑模式擴充套件，在俺寫文的這時候已經超過了三百萬次的下載次數，現在就讓我來記錄一下這款擴充套件的一些特色唄。&lt;/p&gt;
&lt;p&gt;照慣例先上個預覽圖：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OOEraiY.webp&#34;
  alt=&#34;&#34;width=&#34;1722&#34; height=&#34;891&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當我們安裝完成之後，就可以從 Chrome 右上角的擴充套件區看到 DarkReader 的 icon。點開就可以看到它的選單：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/rveo4TK.webp&#34;
  alt=&#34;&#34;width=&#34;345&#34; height=&#34;645&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;由上而下分別就是最常用的主要調整，左上角可以選擇是否套用，右上則是套件開啟關閉和快捷鍵。接著是一些對比度、亮度等等的調整，可以隨著個人喜好條找到舒適的程度。&lt;/p&gt;
&lt;p&gt;其中我覺得最中意的就是最下面的「&lt;strong&gt;僅適用於ＯＯＯ&lt;/strong&gt;」的功能，這代表我們可以將上面的設定&lt;strong&gt;僅僅針對某個特定網站作變更&lt;/strong&gt;，而不會遇到在Ａ網站辛辛苦苦調整後覺得順眼了，結果到了Ｂ網站同樣的配置卻相當刺眼的狀況。&lt;/p&gt;
&lt;p&gt;如此一來，對於某幾個常用的網站，我們完全可以自主微調再套用，甚至針對一些不太適合轉黑色的網站也能夠選擇關閉，這個彈性對我來說是相當實用的功能，幾乎是我最後愛用這款暗黑模式套件的主因。&lt;/p&gt;
&lt;p&gt;這些紀錄是否要開啟黑色模式的網站，就會記錄在第二個「網站列表」的頁籤。雖然可以手動增加，但我個人是不常用到，在首頁設定即可。&lt;/p&gt;
&lt;p&gt;而在第三個「更多」的頁籤，就有一些有趣的設定可以調整了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2IMcXW4.webp&#34;
  alt=&#34;&#34;width=&#34;349&#34; height=&#34;650&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;首先是文字描邊的部分。由於黑暗主題下的白色字會 &lt;a href=&#34;https://www.u-walker.com/news/2464.html&#34;&gt;讓人覺得字的筆劃較粗&lt;/a&gt;，因此大多時候字體就會經過調整。但如果覺得太超過了太細怎麼辦呢？這邊可以調整&lt;strong&gt;文字描邊&lt;/strong&gt;，讓字看起來不要看不見，也不要過眩過粗。&lt;/p&gt;
&lt;p&gt;再往下則是有四種模式可以設定，預設是動態模式，這邊直接上圖給各位感受一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;過濾



&lt;img
  src=&#34;https://image.igouist.net/6I58vhi.webp&#34;
  alt=&#34;&#34;width=&#34;1625&#34; height=&#34;857&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;過濾+



&lt;img
  src=&#34;https://image.igouist.net/FvCr7iQ.webp&#34;
  alt=&#34;&#34;width=&#34;1695&#34; height=&#34;903&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;-靜態



&lt;img
  src=&#34;https://image.igouist.net/NISE8xa.webp&#34;
  alt=&#34;&#34;width=&#34;1692&#34; height=&#34;894&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;-動態



&lt;img
  src=&#34;https://image.igouist.net/sQ6YreP.webp&#34;
  alt=&#34;&#34;width=&#34;1698&#34; height=&#34;844&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其中靜態會有一個編輯畫面，可以自己加入 CSS 達到完全客製。&lt;/p&gt;
&lt;p&gt;而動態則是就交給 Dark Reader 幫你算，像我這種懶人仔就是一路動態到底；像我這種會喜歡 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme&#34;&gt;One Dark Pro&lt;/a&gt; 主題，這類非純黑、漸層質感配色的人，動態的結果也比較符合我的口味
。&lt;/p&gt;
&lt;p&gt;當然，動態產生的 CSS 也是能修改的，點選下面的開發者工具就可以看到完整的 CSS 囉。像本部落格也是基於 &lt;a href=&#34;https://github.com/flysnow-org/maupassant-hugo&#34;&gt;maupassant&lt;/a&gt; 這款好看的白色主題，再加上 Dark Reader 處理後產生的暗黑模式當底來逐步修改出來的。這方面真的要感謝製作主題和套件的大大們呢。&lt;/p&gt;
&lt;p&gt;今天的介紹就到這裡囉，推薦喜歡暗黑模式又想懶人用套件的朋友可以試試這款套件，真的相當好用！那麼，我們下次見～&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (12): 里氏替換原則</title>
      <link>https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle/</link>
      <pubDate>Sun, 01 Nov 2020 11:31:44 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZAuxFRy.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#里氏替換原則-liskov-substitution-principle&#34;&gt;里氏替換原則 (Liskov Substitution Principle)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#我的子類別進入叛逆期了怎麼辦&#34;&gt;我的子類別進入叛逆期了，怎麼辦？&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#所以我們該如何遵守里氏替換原則&#34;&gt;所以，我們該如何遵守里氏替換原則？&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#稍微想一想你可以不要隨便繼承&#34;&gt;稍微想一想，你可以不要（隨便）繼承&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#同系列文章&#34;&gt;同系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;里氏替換原則-liskov-substitution-principle&#34;&gt;里氏替換原則 (Liskov Substitution Principle)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;子類別必須能夠替換父類別&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;並且，子類別替換父類別後，&lt;strong&gt;不需要修改，也不該發生任何錯誤或異常&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;從定義就可以看出來，這項原則是來替我們處理繼承問題的。因此，在開始本篇之前，可能需要先對 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;繼承&lt;/a&gt; 以及 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;多型&lt;/a&gt; 有基本的認識。如果可以的話，也請先看過 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;那麼，就讓我們從很久很久以前開始說起…&lt;/p&gt;
&lt;h2 id=&#34;我的子類別進入叛逆期了怎麼辦&#34;&gt;我的子類別進入叛逆期了，怎麼辦？&lt;/h2&gt;
&lt;p&gt;很久很久以前，有一間公司受到 &lt;a href=&#34;https://zh.wikipedia.org/wiki/%E4%BB%A5%E9%B8%9F%E7%B1%BB%E4%B8%BA%E8%BD%BD%E4%BD%93%E7%9A%84%E7%BD%91%E9%99%85%E5%8D%8F%E8%AE%AE&#34;&gt;鴿子封包&lt;/a&gt; 所啟發，打算發展鳥類運輸技術，強勢打入無人機市場，用生物智慧掀起對人工智慧的革命。既然&lt;strong&gt;鳥類都會飛行&lt;/strong&gt;，理所當然可以藉由飛行來進行空運，甚至還可以偷偷擊墜那些無人機對手，野心勃勃的老闆立馬徵了一批鳥類物流士，打出「凡是鳥類都可應徵」的旗號，各式各樣的猛禽響應而來，一時之間掀起整個物流業的風暴！&lt;/p&gt;
&lt;p&gt;但是好景不常，公司營運之後發貨狀況不佳，頻繁發生丟包問題，甚至有些貨根本就出不了倉庫，虧損越來越大，心急如焚的老闆下令徹查，這才發現—&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;企鵝是鳥，企鵝不會飛&lt;/strong&gt;。一堆企鵝在倉庫門口發呆。&lt;/p&gt;
&lt;p&gt;但是一切已經來不及，虧損已經造成，這家鳥禽物流公司最後也慢慢消失在塵埃之中……&lt;/p&gt;
&lt;p&gt;這個故事告訴我們：如果子類別（企鵝）沒有達到我們對父類別（鳥）的期待，就很容易在不知不覺中出事！&lt;/p&gt;
&lt;p&gt;我們已經預期了「鳥＝會飛行」這個前提，但繼承的企鵝卻無法實作飛行，如此就會讓我們被誤導、在使用時&lt;strong&gt;誤入陷阱&lt;/strong&gt;。這種子類繼承時搞叛逆，和父類別行為相違所發生的問題，難以預期也難以察覺，絕對是我輩不能容忍的。因此，里氏替換原則就出現了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：感謝這篇 &lt;a href=&#34;https://medium.com/@f40507777/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8F%9B%E5%8E%9F%E5%89%87-liskov-substitution-principle-adc1650ada53&#34;&gt;里氏替換原則 Liskov Substitution Principle (LSP) - Finn&lt;/a&gt; 的附圖，我之後就想不出比企鵝更貼切的例子了囧&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;所以我們該如何遵守里氏替換原則&#34;&gt;所以，我們該如何遵守里氏替換原則？&lt;/h2&gt;
&lt;p&gt;我們再提一次：子類別必須要能替換掉父類別，而&lt;strong&gt;不需要改變&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我們在 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;多型篇&lt;/a&gt; 的時候提過「用子類別實作出各式各樣不同的方法，藉此讓父類別的方法藉此達到延伸和多樣化的效果」如此我們的物件彼此之間才能保持彈性，擁有可替換可擴充的特性，進而達到 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;開放封閉原則&lt;/a&gt; 所要求的：&lt;strong&gt;對修改封閉（不需要修改使用到父類別的地方），對擴展開放（而是只需要用子類別進行擴充，就能完成變動）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然而，這個擴展不該是天馬行空隨便亂擴的，必須要有原則。&lt;/p&gt;
&lt;p&gt;最首要的就是：至少&lt;strong&gt;父類別能做到的事情，子類別也要能做到&lt;/strong&gt;，不能說今天換成子類別就整組壞光光。畢竟，如果原本的東西變少了或壞掉了，那就不叫延伸了，對吧？&lt;/p&gt;
&lt;p&gt;也就是說，一個好的擴展方式，應該能滿足這些條件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要求不應該比父類別多&lt;/li&gt;
&lt;li&gt;回饋不應該比父類別少&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如說：爸爸每天都去市場賣香蕉，一支二十，數十年間颳風下雨從未改變。某一天爸爸生病，不想打破這個傳統，就請兒子去代班。這時：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;熟客們知道一支是二十元，他們順路來買香蕉的時候也只會準備二十元。&lt;br/&gt;
所以，兒子不能亂漲價到五十元，因為客人也拿不出來，而且臨時漲價還會被留負評&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;熟客們知道給了錢就可以拿到香蕉，他們給了錢之後就會等著老闆把香蕉給他們。&lt;br/&gt;
所以，兒子不能收了人家二十元，然後只給半支香蕉，客人會很傻眼，攤子會很危險&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些熟客，其實就是我們工程師。&lt;strong&gt;我們預期了這個函式或類別需要準備的輸入參數，也預期了應該要有的輸出結果。如果某一天替換了子類別，卻不是這麼一回事，就會發生很多意料外的錯誤&lt;/strong&gt;。對買香蕉這件事而言：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;給足夠的錢就是所謂的「前置條件」或「先驗條件」&lt;/li&gt;
&lt;li&gt;預期拿到香蕉就是「後置條件」或「後驗條件」，&lt;/li&gt;
&lt;li&gt;每天都會去市場賣香蕉就是「不變條件」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此當我們想要符合里氏替換原則時候，其實就可以試著遵守這幾條規則：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先驗條件不可以強化&lt;/strong&gt;：&lt;br/&gt;
父類別要求的是矩形，子類別就不能要求得更嚴，只准人家給正方形&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;後驗條件不可以弱化&lt;/strong&gt;：&lt;br/&gt;
父類別產出的是正方形，子類別不能說沒關係啦，就給人家隨便一個矩形&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不變條件必須保持不變&lt;/strong&gt;：&lt;br/&gt;
父類別是一個產生矩形的方法，子類別不能背骨，跑去產生圓形&lt;/p&gt;
&lt;p&gt;只要確保了&lt;strong&gt;輸入和輸出都是一致的&lt;/strong&gt;，就可以減少很多神奇妙妙問題。這個也就是所謂的&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E5%A5%91%E7%BA%A6%E5%BC%8F%E8%AE%BE%E8%AE%A1&#34;&gt;契約式設計 (Design By Contract)&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&#34;稍微想一想你可以不要隨便繼承&#34;&gt;稍微想一想，你可以不要（隨便）繼承&lt;/h2&gt;
&lt;p&gt;有沒有發現，這個契約式的描述，和我們提過的 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt; 概念是不是很像呢？可以稍微想一想：&lt;strong&gt;介面和繼承&lt;/strong&gt;間的關係，以及&lt;strong&gt;介面與里氏替換原則&lt;/strong&gt;的關係。&lt;/p&gt;
&lt;p&gt;首先，為什麼我們要使用繼承呢？如果只是為了減少重複程式碼，那實在是，呃，相當不建議。&lt;/p&gt;
&lt;p&gt;這邊需要了解一個觀念：&lt;strong&gt;我們不應該因為單純的「IS-A」就濫用繼承，那樣是危險的&lt;/strong&gt;。企鵝「是」鳥類、正方形「是」矩形，在想法上似乎是沒有問題的，但是貿然繼承就會遇到「企鵝不會飛」、「正方形四邊等長」等問題，讓實作上有種綁手綁腳的感覺。&lt;/p&gt;
&lt;p&gt;真正的繼承應該是基於行為的：&lt;strong&gt;這個子類別能不能做到父類別期望的行為&lt;/strong&gt;？這才是里氏替換原則的核心。&lt;/p&gt;
&lt;p&gt;不要用繼承去掠奪父類的程式碼，而是把目光放在行為，試著去思考父類別期望的行為是什麼、哪些是不可變的；期望的前置條件、後置條件，也就是輸入和輸出又代表什麼。&lt;/p&gt;
&lt;p&gt;當我們需要繼承時，就稍微想一想，把&lt;strong&gt;觀看物件的角度集中在它的功能上，去試著了解父類別所期望的繼承方式，和使用者期望的預期結果&lt;/strong&gt;。如此一來，我們自然就會朝向遵守契約式設計精神的&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt;來取代繼承，又或是釐清功能之間的&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;職責&lt;/a&gt;，利用組合各個功能的子模組的方式來完成我們要的行為。&lt;/p&gt;
&lt;p&gt;放下繼承的包袱，了解繼承的原則，才能真正達到多型的精神，這就是里氏替換原則替我們指引出的方向。&lt;/p&gt;
&lt;p&gt;既然我們需要用到介面，那介面又有什麼要注意的地方呢？這就要到我們的介面隔離原則再聊了。欲知後續如何，且待下回分曉。&lt;/p&gt;
&lt;p&gt;那麼，我們下次見～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文主要參考至這幾篇，建議想對里氏替換原則更了解的朋友可以閱讀一下呦：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/12/blog-post_31.html&#34;&gt;物件導向設計原則：里氏替換原則，定義、解析 - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8F%9B%E5%8E%9F%E5%89%87-liskov-substitution-principle-e66659344aed&#34;&gt;使人瘋狂的 SOLID 原則：里氏替換原則 (Liskov Substitution Principle) - 程式愛好者&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@f40507777/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8F%9B%E5%8E%9F%E5%89%87-liskov-substitution-principle-adc1650ada53&#34;&gt;里氏替換原則 Liskov Substitution Principle (LSP) - Finn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8F%9B%E5%8E%9F%E5%89%87-liskov-substitution-principle-e66659344aed&#34;&gt;使人瘋狂的 SOLID 原則：里氏替換原則 (Liskov Substitution Principle) - 程式愛好者&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://teddy-chen-tw.blogspot.com/2012/01/4.html&#34;&gt;亂談軟體設計（4）：Liskov Substitution Principle - 搞笑談軟工&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/12/blog-post_31.html&#34;&gt;物件導向設計原則：里氏替換原則，定義、解析 - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-5_liskov-substitution-principlelsp-%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8F%9B%E5%8E%9F%E5%89%87/&#34;&gt;Object Oriented物件導向設計原則SOLID-5:Liskov Substitution Principle(LSP) 里氏替換原則 - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E5%A5%91%E7%BA%A6%E5%BC%8F%E8%AE%BE%E8%AE%A1&#34;&gt;契約式設計 - 維基百科&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789864342099&#34;&gt;《無瑕的程式碼：物件導向原則、設計模式與C#實踐》&lt;/a&gt; Ch.10 LSP －－Liskov替換原則&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (11): 開放封閉原則</title>
      <link>https://igouist.github.io/post/2020/10/oo-11-open-closed-principle/</link>
      <pubDate>Sun, 25 Oct 2020 11:58:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/10/oo-11-open-closed-principle/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6pQOti2.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#開放封閉原則-open-close-principle&#34;&gt;開放封閉原則 (Open-Close Principle)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#模組化&#34;&gt;模組化&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#實行&#34;&gt;實行&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#結語&#34;&gt;結語&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#同系列文章&#34;&gt;同系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;開放封閉原則-open-close-principle&#34;&gt;開放封閉原則 (Open-Close Principle)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;軟體實體（類別、模組、函式等等）應該對擴展開放，而對修改封閉&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在我們了解什麼是「對擴展開放」和「對修改封閉」之前，先讓我們談談：什麼是擴展，什麼又是修改呢？&lt;/p&gt;
&lt;p&gt;用白話一點的方式來形容，修改就是把東西拆開來改，像是手術；而擴展就是對東西額外加裝模組，像是添購設備。我們用飛行來舉例，像是鳥類直接用翅膀飛行，如果有需要修改飛行方法的話就得對鳥直接進行手術；但如果今天是一個裝備了噴射背包的人，我們只需要把噴射背包換成噴射鞋子、甚至噴射翅膀就可以了，不需要去修改人這個本體。&lt;/p&gt;
&lt;p&gt;這邊可以發現開放封閉原則是針對「改變的時候」去做一個行動的建議，例如需求追加和變更等等。&lt;strong&gt;凡是變化都有成本&lt;/strong&gt;，例如變動的難易度、變動造成的影響範圍等等都會影響到成本，若是程式碼冗長、內部邏輯複雜，類別之間互相耦合、影響範圍很廣，導致綁手綁腳或壞東壞西等等狀況，使得修改很困難，成本就會變高，進而使得開發效率變低。&lt;/p&gt;
&lt;p&gt;然而，軟體並不是製造完畢就完工的東西，而是隨需求而生、隨需求而變的動態作品，因此程式碼的修改或重構相當頻繁。就像我們在 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;內聚耦合篇&lt;/a&gt; 提過的：軟體面對改變的能力，就像基因適應環境並生存下去的能力。因此，程式必須具有彈性，也就是需要盡可能降低修改的成本。&lt;/p&gt;
&lt;p&gt;那麼讓我們回到前面：動手術跟換道具，哪個的成本比較高呢？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;面對需求，對程式碼的改動是透過增加新程式碼進行的，而不是更改現有的程式碼&lt;/strong&gt;　　
&lt;br/&gt;（《大話設計模式》）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;所以，我們希望能夠用擴充的方式去完成變化，而不是用針對內部進行修改的方式來做；希望藉由良好的設計，能迴避上面那串修改困難導致成本高昂的問題。&lt;/p&gt;
&lt;p&gt;而這個思路，其實你我都已經很習以為常了，就是&lt;strong&gt;模組化&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;模組化&#34;&gt;模組化&lt;/h2&gt;
&lt;p&gt;組裝電腦的時候，就是針對主機板加裝各種模組；寫程式的時候，我們也很習慣引入套件來使用；甚至使用 Visual Studio 或是 Chrome 這類軟體的時候，我們也都會使用擴充套件來加上我們需要的功能。甚至洛克人也是，我們打倒 BOSS 之後就能替洛克人加裝各種模組，讓他能夠具備各個 BOSS 的功能。噢當然我們不會把整台洛克人拆開然後改造成另一台，不然拆來拆去多麻煩。&lt;/p&gt;
&lt;p&gt;我們的周遭四處可見模組化，用模組來擴充本體的想法自古以來比比皆然。&lt;/p&gt;
&lt;p&gt;這邊就可以發現到：主機板上面事先會留好許多讓你接顯示卡或記憶體等等的插槽、Chrome 這類軟體會開放 API 和權限等功能給擴充套件來使用。當我們想要利用擴展的方式來擴充本體的能力時，我們需要留下一個供對接的地方，也就是&lt;strong&gt;擴充點&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;擴充點可以說是留給後人的貼心禮物（跟另一種留給後人的炸死人禮物並不相同）但是，我們要怎麼知道哪些地方可以擴充、可能擴充呢？我們可以先區分&lt;strong&gt;主要邏輯&lt;/strong&gt;和&lt;strong&gt;附加邏輯&lt;/strong&gt;，像是洛克人跟技能，你和噴射背包，又或者是「查詢客戶」的主邏輯和各種不同「查詢客戶的條件」等等的組合。&lt;/p&gt;
&lt;p&gt;因為如果不加以區分，我們就沒辦法把附加邏輯做成模組，也就找不到主要邏輯和附加邏輯之間的擴充點，如此一來就勢必要針對混成一坨的邏輯做修改和業務處理，接著就會落入我們在上一篇 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;單一職責原則&lt;/a&gt; 提過的各種悲慘下場：修改一個地方影響一狗票功能、修改前必須痛苦地閱讀大量不相關的程式碼…等等。&lt;/p&gt;
&lt;p&gt;這些問題，也正是&lt;strong&gt;單一職責原則&lt;/strong&gt;所要解決的。此處可以直接參考上一篇也引用過的 &lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/12/blog-post.html&#34;&gt;再談物件導向設計原則: 單一職責原則，定義、解析與實踐&lt;/a&gt; 這篇，裡面的學生列表例子就蠻直接好懂的。&lt;/p&gt;
&lt;p&gt;另外也必須推薦一下這位大大的 &lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/12/blog-post_11.html&#34;&gt;物件導向設計原則：開放封閉原則，定義、解析與實踐&lt;/a&gt; ，對業務邏輯和附加邏輯的說明也相當明確，從為什麼要隔離兩者，到如何實踐都有說明，值得一看。&lt;/p&gt;
&lt;p&gt;而我們辨認出主要邏輯跟附加邏輯之後，該怎麼實行開放封閉原則呢？&lt;/p&gt;
&lt;h2 id=&#34;實行&#34;&gt;實行&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;答案就是抽象。（《無瑕的程式碼：敏捷完整篇》）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這邊舉幾個方向：我們可以&lt;strong&gt;在主要邏輯和附加邏輯之間，加入抽象層來解耦合&lt;/strong&gt;，也就是我們 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt; 大哥該出場的時候了。當我們的類別不再堅持依賴某個物件，例如說我就是要噴射背包，然後把背包黏死在背後；而是接受我只需要能飛的東西，不論傳遞進來的是噴射背包還是噴射鞋子，如此一來就夠用介面表達出需求，使得功能&lt;strong&gt;可以被任何符合需求的方式擴展&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;另外還有，使用外部注入來處理附加邏輯。除了不將附加邏輯寫在類別中，降低修改的機會以外，和介面的邏輯一致：你給什麼工具我就用什麼工具。當我們的附加邏輯是從外部丟給類別，使得類別預先留好擴充點，並且能由外部決定擴充方式，要擴展也就相當容易了。順便一提，我進公司學習以來，注入跟介面通常都是一起出現的 Combo 技。&lt;/p&gt;
&lt;p&gt;當我們在設置我們的擴充點（上個世紀稱作「放置鉤子」）時，有時會預測失敗，變成&lt;strong&gt;不必要的複雜性&lt;/strong&gt;。也很容易走火入魔，就變成&lt;strong&gt;過度設計&lt;/strong&gt;。因此，我們最終會等到足夠確信將會變化時，才進行重構的動作。&lt;/p&gt;
&lt;p&gt;在無瑕的程式碼中，建議可以接受「&lt;strong&gt;被愚弄一次&lt;/strong&gt;」，先假設不會變化，而當真的變化到來時，就將該變化相關的部份重構抽象起來，得了一次病，從此就免疫，還可以少走冤枉路。又或許，也可以嘗試看看 &lt;a href=&#34;https://shawnlin0201.github.io/Methodology/Methodology-004-Rule-Of-Three-principle/&#34;&gt;三次原則&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.09 補充：&lt;br/&gt;看見 Huanlin 大大寫了這篇 &lt;a href=&#34;https://www.huanlintalk.com/2024/09/avoid-premature-abstraction.html&#34;&gt;避免過早的抽象設計&lt;/a&gt;，覺得和「被愚弄一次」的精神相似。最近也在體會過早抽象帶來的痛苦，決定把這篇補充回來，給正在認識 OCP 的朋朋們參考。&lt;/p&gt;
&lt;p&gt;OCP 能幫助我們思考並寫出乾淨彈性的程式碼，但切記不要走火入魔，否則反而會帶來更多維護的成本。謹記三次重構原則，保一世平安，阿彌陀佛。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;結語&#34;&gt;結語&lt;/h2&gt;
&lt;p&gt;最後，開放封閉原則的範圍實在是太大了。事實上，其他設計原則，例如單一職責、依賴反轉等等，都是為了達到開放封閉這個終極的目標而產生的。但是，我們不可能預測到所有變化，也沒有任何做法能夠適用於所有狀況，因此要達到完全的封閉是不可能的。然而，這是我們應當嘗試精進的目標，只要謹記開放封閉原則，就能不斷改善架構，也就離良好的設計更進一步了。&lt;/p&gt;
&lt;p&gt;而對我而言，開放封閉的好處在於&lt;strong&gt;強迫像我這樣的工程師去思考：哪些地方是附加邏輯，哪些地方可以留作擴充，又該怎麼做才能方便擴充&lt;/strong&gt;，這個過程和嘗試對我輩菜鳥才是真正最有價值的地方吧。&lt;/p&gt;
&lt;p&gt;最後感謝一下 Ray 大大的路過指點。我當時問了不知道怎麼形容開放封閉原則，大大就說了個例子：咱們人哪，學新東西可是比改個性來得簡單多了。也基於這個例子讓我想到了手術和洛克人，還有變動的難易度、本性（核心邏輯）和新技能（附加邏輯）的差別可以這樣咻咻地串起來，這邊就謝過啦。順便也貼下大大的 &lt;a href=&#34;https://raychiutw.github.io/&#34;&gt;Blog&lt;/a&gt;，加減蹭一下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jyt0532.com/2020/03/19/ocp/&#34;&gt;深入淺出開放封閉原則 Open-Closed Principle - jyt0532&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/12/blog-post_11.html&#34;&gt;物件導向設計原則：開放封閉原則，定義、解析與實踐 - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E6%89%8B%E5%AF%AB%E7%AD%86%E8%A8%98/%E7%AC%AC-10-%E7%AB%A0-%E9%A1%9E%E5%88%A5-clean-code-1c7898d11cd7&#34;&gt;第 10 章 類別 | Clean Code 敏捷軟體開發技巧守則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://shawnlin0201.github.io/Methodology/Methodology-004-Rule-Of-Three-principle/&#34;&gt;程式設計心法 三次原則（Rule Of Three principle）- 璇之又璇的網路世界&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-2_open-close-principleocp-%E9%96%8B%E6%94%BE%E5%B0%81%E9%96%89%E5%8E%9F%E5%89%87/&#34;&gt;Object Oriented物件導向設計原則SOLID-2:Open-Close Principle(OCP) 開放封閉原則 - Sian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞的 Markdown 筆記</title>
      <link>https://igouist.github.io/post/2020/10/markdown/</link>
      <pubDate>Sun, 18 Oct 2020 22:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/10/markdown/</guid>
      <description>&lt;p&gt;Markdown 是一種寫作用語言，特色是只要用簡單的符號就可以替文章進行排版，例如 &lt;code&gt;#&lt;/code&gt; 就代表了標題，因此能相當簡潔迅速地應用 Markdown 語法來撰寫出文件，目前已經被廣泛使用在各個撰寫文章或是文檔的場景中。&lt;/p&gt;
&lt;p&gt;例如 Github 用來說明專案的 Readme.md，從副檔名 md 就已經告訴你這是一篇 Markdown；又像是這個部落格的文章，也都是使用 markdown 來寫的。除此之外，像是 Facebook 和 Line 都開始支援簡單的 Markdown 語法了 —— 因為它實在是太方便好用了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用Markdown格式撰寫的文件應該可以直接以純文字發佈，並且看起來不會像是由許多標籤或是格式指令所構成 —— markdown.tw&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;既然用簡單的符號就能完成這些簡潔的排版，我們自然就能把專注的重心挪回到撰寫文章本身，這也就是 Markdown 最大的魅力：&lt;strong&gt;專注於內容&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也因為 Markdown 的特色就是非常的簡潔乾淨，文檔本身的可讀性就相當的高，撰寫起來也很直覺容易。就如同其說明文件所說的：「Markdown 的目標就是實現『&lt;strong&gt;易讀易寫&lt;/strong&gt;』」&lt;/p&gt;
&lt;p&gt;這篇就來稍微紀錄一下 Markdown 的常用語法和好用的編輯環境吧！&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#語法&#34;&gt;語法&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#標題&#34;&gt;標題&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#分隔線&#34;&gt;分隔線&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#斜體&#34;&gt;斜體&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#粗體&#34;&gt;粗體&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#列表&#34;&gt;列表&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#核取方塊&#34;&gt;核取方塊&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#引用&#34;&gt;引用&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#連結&#34;&gt;連結&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#圖片&#34;&gt;圖片&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#表格&#34;&gt;表格&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#程式碼區塊&#34;&gt;程式碼區塊&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#其他&#34;&gt;其他&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#工具&#34;&gt;工具&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#hackmd&#34;&gt;Hackmd&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#typora&#34;&gt;Typora&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#visual-studio-code&#34;&gt;Visual Studio Code&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#總結&#34;&gt;總結&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;語法&#34;&gt;語法&lt;/h2&gt;
&lt;h3 id=&#34;標題&#34;&gt;標題&lt;/h3&gt;
&lt;p&gt;Markdown 中要加入標題，只需要在開頭加上 &lt;code&gt;#&lt;/code&gt; 就可以了。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 標題一 H1
## 標題二 H2
### 標題三 H3
#### 標題四 H4
##### 標題五 H5
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/W20kTy7.webp&#34;
  alt=&#34;&#34;width=&#34;771&#34; height=&#34;297&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;或是使用底線標示的方式也可以告訴 Markdown 這是標題，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;標題一 H1
========

標題二 H2
--------
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/q715Wbr.webp&#34;
  alt=&#34;&#34;width=&#34;755&#34; height=&#34;161&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;分隔線&#34;&gt;分隔線&lt;/h3&gt;
&lt;p&gt;如果是想區分段落，不想要輸入標題，也可以直接用分隔線 &lt;code&gt;---&lt;/code&gt;，但記得要空一行，不然會被誤認為標題，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;我是紅海

---

我是紅海
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/cxLiHqU.webp&#34;
  alt=&#34;&#34;width=&#34;760&#34; height=&#34;122&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;斜體&#34;&gt;斜體&lt;/h3&gt;
&lt;p&gt;當我們需要斜體的時候，可以用 &lt;code&gt;*&lt;/code&gt; 或 &lt;code&gt;_&lt;/code&gt; 來將文字包起來，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;我是 *斜體* 啦！會有一點 _斜斜的_
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PPq8IzB.webp&#34;
  alt=&#34;&#34;width=&#34;264&#34; height=&#34;39&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;粗體&#34;&gt;粗體&lt;/h3&gt;
&lt;p&gt;當如果需要強調某個語句或段落，則可以用 &lt;code&gt;**&lt;/code&gt; 或是 &lt;code&gt;__&lt;/code&gt; 來將文字包起來，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;我是 **粗體** 啦！這一段 __很重要__ 哦！
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IZXInCx.webp&#34;
  alt=&#34;&#34;width=&#34;286&#34; height=&#34;39&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;列表&#34;&gt;列表&lt;/h3&gt;
&lt;p&gt;列表分為有序和無序，其中有序的使用數字加上點來標示，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. 我是第一項
    1. 我是第一項的第一小項
    2. 我是第一項的第二小項
2. 我是第二項
3. 我是第三項
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MZRCdPV.webp&#34;
  alt=&#34;&#34;width=&#34;299&#34; height=&#34;160&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而無序清單則可以用 &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt; 來標示，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;- 蘋果
- 柳橙
- 香蕉
    - 香蕉？
    - 香蕉！
- 水蜜桃
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/O9kA2yG.webp&#34;
  alt=&#34;&#34;width=&#34;235&#34; height=&#34;189&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;核取方塊&#34;&gt;核取方塊&lt;/h3&gt;
&lt;p&gt;當我們需要建立待辦事項這類需要勾選的列表時，就可以考慮用核取方塊 &lt;code&gt;[ ]&lt;/code&gt; 和 &lt;code&gt;[x]&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;- [ ] 買蘋果
- [ ] 買柳橙
- [x] 買香蕉
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Ita9Wko.webp&#34;
  alt=&#34;&#34;width=&#34;161&#34; height=&#34;110&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;引用&#34;&gt;引用&lt;/h3&gt;
&lt;p&gt;需要引用某個片段或特別標示的時候，可以用 &lt;code&gt;&amp;gt;&lt;/code&gt;，例如：&lt;code&gt;&amp;gt; 我是一句名言&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;紙上得來終覺淺，絕知此事要躬行&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;大多數平台也會支援巢狀的用法，例如：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;子非魚，安知魚之樂？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;子非我，安知我不知魚之樂？&lt;/p&gt;&lt;/blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/AdMF7DC.webp&#34;
  alt=&#34;&#34;width=&#34;1080&#34; height=&#34;384&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;連結&#34;&gt;連結&lt;/h3&gt;
&lt;p&gt;需要加入連結的時候，可以使用 &lt;code&gt;[連結文字](網址)&lt;/code&gt;，例如當我要插入 Markdown.tw 的說明時就可以：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;請參見 [Markdown 語法說明](https://markdown.tw/)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/e8D1yFX.webp&#34;
  alt=&#34;&#34;width=&#34;539&#34; height=&#34;99&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;圖片&#34;&gt;圖片&lt;/h3&gt;
&lt;p&gt;當連結是圖片的時候，只需要在最前面加入 &lt;code&gt;!&lt;/code&gt; 變成 &lt;code&gt;![](圖片網址)&lt;/code&gt; 就可以囉，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;![](https://avatars2.githubusercontent.com/u/16403463?s=460&amp;amp;u=fccdf65b21cb2dc5c544c3b473f135f00c574030&amp;amp;v=4)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;就會跑出我家的貓（插入圖片也太難擷取成示意圖了吧，各位意會就好）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://avatars2.githubusercontent.com/u/16403463?s=460&amp;amp;u=fccdf65b21cb2dc5c544c3b473f135f00c574030&amp;amp;v=4&#34;
  alt=&#34;&#34;width=&#34;450&#34; height=&#34;450&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;表格&#34;&gt;表格&lt;/h3&gt;
&lt;p&gt;表格應該是我在 Markdown 中覺得不太親切的東西了…&lt;/p&gt;
&lt;p&gt;其實就是要你直接畫一個表格出來（排版請見諒，已盡力）：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;| 水果名稱 | 好吃程度 | 昂貴程度 | 爆炸程度 |
| --- | --- | --- | --- |
| 蘋果 | 高 | 高 | 高 |
| 香蕉 | 中 | 低 | 無 |
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yDwiiqK.webp&#34;
  alt=&#34;&#34;width=&#34;423&#34; height=&#34;139&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;也可以設定置中或靠右，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;| 靠左 | 置中 | 靠右 |
| :--- | :---: | ---: |
| 芭樂 | 奇異果 | 蓮霧 |
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/EXSKe9T.webp&#34;
  alt=&#34;&#34;width=&#34;226&#34; height=&#34;100&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;程式碼區塊&#34;&gt;程式碼區塊&lt;/h3&gt;
&lt;p&gt;程式碼區塊使用反引號 &lt;code&gt;`&lt;/code&gt;，就是鍵盤左上角那個。如果只用一對把文字包起來就會是單行，例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;你可以使用 `Hello()` 這個語法來打招呼
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HSlF7UZ.webp&#34;
  alt=&#34;&#34;width=&#34;332&#34; height=&#34;36&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而如果用三個反引號為一組，就可以產生程式碼區塊，例如：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fl5MDQs.webp&#34;
  alt=&#34;&#34;width=&#34;377&#34; height=&#34;75&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MdtrbVx.webp&#34;
  alt=&#34;&#34;width=&#34;741&#34; height=&#34;78&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有些平台會支援程式碼上色，這時候只要在第一組反引號後面加上程式語言就可以上色，例如
：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BfrNXyR.webp&#34;
  alt=&#34;&#34;width=&#34;563&#34; height=&#34;168&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main(&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;[] args)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello World!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;另外有些平台也支援行號顯示，像 Hackmd 只需要在反組號的程式語言後加上等號就會顯示行號，例如 &lt;code&gt;csharp=&lt;/code&gt;，而我現在用的部落格主題，則是要到 config 統一配置才會顯示。&lt;/p&gt;
&lt;p&gt;由於 Markdown 力求簡潔，所以這些比較延伸的語法，通常需要看一下使用的編輯器是怎麼支援的。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Xi48nAm.webp&#34;
  alt=&#34;&#34;width=&#34;564&#34; height=&#34;177&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;其他&#34;&gt;其他&lt;/h3&gt;
&lt;p&gt;另外隨著平台的支援，可能還有提供其他語法。例如用一或是兩個 &lt;code&gt;~&lt;/code&gt; 將文字包起來就常用於表達刪除號。提供寫作的平台多少都會有說明，開工前可以先翻翻看，畢竟語法糖不嫌多嘛。&lt;/p&gt;
&lt;h2 id=&#34;工具&#34;&gt;工具&lt;/h2&gt;
&lt;p&gt;接著讓我們介紹一些撰寫 Markdown 時的方便工具，以下這些是我比較常接觸的 Markdown 編輯環境，如果有什麼也挺不錯的還請告訴我一聲呦&lt;/p&gt;
&lt;h3 id=&#34;hackmd&#34;&gt;Hackmd&lt;/h3&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/goVhwKW.webp&#34;
  alt=&#34;&#34;width=&#34;1897&#34; height=&#34;877&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://hackmd.io/&#34;&gt;Hackmd&lt;/a&gt; 一定是必須推薦的！它是我目前做為主力的 Markdown 編輯器，它能夠：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提供了方便的線上編輯環境，能用雙欄同時編輯和檢視&lt;/li&gt;
&lt;li&gt;多人協作&lt;/li&gt;
&lt;li&gt;貼上圖片自動上傳到 imgur&lt;/li&gt;
&lt;li&gt;可以嵌入 Youtube 影片等等&lt;/li&gt;
&lt;li&gt;將 md 檔直接儲存到 Github 或 Dropbox 等等空間&lt;/li&gt;
&lt;li&gt;可以使用&lt;a href=&#34;https://hackmd.io/c/tutorials-tw/%2Fs%2FMathJax-and-UML-tw&#34;&gt;語法&lt;/a&gt;直接產生 LaTeX、UML、流程圖甚至五線譜&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外還能夠將筆記分享給其他人，或是直接公開到網路上，甚至將筆記整理起來整理成&lt;a href=&#34;https://hackmd.io/s/how-to-create-book-tw&#34;&gt;書本&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;支援的用法相當多，打開瀏覽器就能用，可以說是使用 Mackdown 寫作的工具首選！甚至不少人直接當成團隊文檔庫或部落格在使用。有興趣的朋友，就從它的&lt;a href=&#34;https://hackmd.io/c/tutorials-tw/%2Fs%2Ftutorials-tw&#34;&gt;使用教學&lt;/a&gt;開始試試吧！&lt;/p&gt;
&lt;h3 id=&#34;typora&#34;&gt;Typora&lt;/h3&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CNC48VZ.webp&#34;
  alt=&#34;&#34;width=&#34;1009&#34; height=&#34;909&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;對我來說最方便的 Markdown 寫作環境是 Hackmd 的話，那最美的 Markdown 寫作環境就是 Typora 了。直接前往 &lt;a href=&#34;https://typora.io/&#34;&gt;Typora 的網站&lt;/a&gt; 就可以感受到它的那種極簡風格。&lt;/p&gt;
&lt;p&gt;另外 Typora 也支持使用自訂主題，只要從設定中打開主題資料夾，把 CSS 丟進去就好了，因此可以上社群（例如熟悉的 One Dark）甚至自製主題來讓編輯器更好看。&lt;/p&gt;
&lt;p&gt;Typora 也支援將文檔製作成 PDF 的功能，並且這個 PDF 是吃得到主題的 CSS 的！像我自己的履歷表就是用 Typora 寫完之後，調整 CSS 直接壓成 PDF 的，這邊也推薦給大家。&lt;/p&gt;
&lt;p&gt;改得順眼之後只能用舒服優雅來形容，寫起來都賞心悅目了，推薦熟悉 Markdown 語法之後可以試試。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SChJiOf.webp&#34;
  alt=&#34;&#34;width=&#34;781&#34; height=&#34;940&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;但記得要先去偏好設定改成一體化視窗，並且去主題資料夾稍微修改一下字型讓中文舒服一點，像附圖就是使用了&lt;a href=&#34;https://github.com/be5invis/Sarasa-Gothic&#34;&gt;更紗黑體&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;整體來說，如果你要去咖啡廳之類的寫文章，那就真的可以帶 Typora 回去改個自己喜歡的主題，真的舒服。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更新: Typera 也有主題下載頁囉！可以從 &lt;code&gt;設定 → 偏好設定&lt;/code&gt; 前往&lt;/p&gt;
&lt;p&gt;或是直接到 &lt;a href=&#34;https://theme.typora.io/&#34;&gt;https://theme.typora.io/&lt;/a&gt; 下載主題&lt;/p&gt;
&lt;p&gt;這邊也推薦一組很棒的主題：&lt;a href=&#34;https://github.com/liangjingkanji/DrakeTyporaTheme&#34;&gt;DrakeTyporaTheme&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充: 想要跟 HackMD 一樣能夠直接貼上圖片上傳的朋友，也可以參考這兩篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pjchender.blogspot.com/2020/08/app-typora-imgur-for-mac.html&#34;&gt;Typora 自動上傳圖片到 imgur (for Mac)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/130878433&#34;&gt;Windows 下采用 Typora + PicGo 搭建 Markdown 图床&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;visual-studio-code&#34;&gt;Visual Studio Code&lt;/h3&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/EDccuCQ.webp&#34;
  alt=&#34;&#34;width=&#34;2150&#34; height=&#34;1041&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;del&gt;那真是太諷刺了紹安&lt;/del&gt;，繞了一圈最後最常用的還是 Visual Studio Code 直接開寫，畢竟 Hugo 寫部落格的時候也是在本機寫好推上去，過一陣子就懶得開東開西，只想直接開工。所以還是直接用 VSCode 最快了。&lt;/p&gt;
&lt;p&gt;安裝一下 Markdown 的套件之後就能直接開工了，我個人是安裝 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one&#34;&gt;Markdown All in One&lt;/a&gt;，該有的都有了。&lt;/p&gt;
&lt;p&gt;不過上面的 Hackmd 也有推出 VScode 用的 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=HackMD.vscode-hackmd&#34;&gt;Hackmd&lt;/a&gt; 套件包，像上面提到的繪製流程圖、數學式等功能也包含在內，有這類需求的朋友也可以使用看看。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充: 想要跟 HackMD 一樣能夠直接貼上圖片上傳的朋友，也可以試試 vscode-imgur 這套插件呦&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;總結&#34;&gt;總結&lt;/h2&gt;
&lt;p&gt;這篇記錄了一些常用的 Markdown 語法，並且推薦了三款 Markdown 編輯器，有興趣的朋友可以嘗試用 Markdown 打打文章或是規範文檔，相信一定能感受到 Markdown 的魅力。那麼，我們下周見～&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://markdown.tw/&#34;&gt;Markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wcc723.github.io/development/2019/11/23/ten-mins-learn-markdown/&#34;&gt;十分鐘快速掌握 Markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10203758&#34;&gt;Markdown - 易編易讀，優雅的寫文吧！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=vlFm3EVVj6Y&#34;&gt;筆記＆寫作神器 MarkDown 真希望我學生時期就懂！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pjchender.blogspot.com/2020/08/app-typora-imgur-for-mac.html&#34;&gt;Typora 自動上傳圖片到 imgur (for Mac) - PJCHENder 那些沒告訴你的小細節&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>外接螢幕初體驗</title>
      <link>https://igouist.github.io/post/2020/10/external-screen/</link>
      <pubDate>Sun, 11 Oct 2020 23:50:17 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/10/external-screen/</guid>
      <description>&lt;p&gt;這週入手了新玩具，在這邊記錄下～&lt;/p&gt;
&lt;p&gt;&lt;s&gt;我們家大神大大跟我說部落格就當推特發就對了，唉呀我也是深表認同吶&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5BHHs9a.webp&#34;
  alt=&#34;&#34;width=&#34;1706&#34; height=&#34;960&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;像我這種被實驗室和公司寵壞的人，已經習慣了雙螢幕的好。結果不管是在放不下兩台螢幕的家裡書桌，還是帶著筆電出門，尤其是一邊寫東西一邊查資料，需要來來回回切換視窗時，總是會想「唉呀真想把這丟到另一個螢幕啊！」&lt;/p&gt;
&lt;p&gt;因此！幾經掙扎之後，還是入手了外接螢幕！&lt;/p&gt;
&lt;p&gt;開場先說心得，雖然尚未有外出機會，但目前的使用相當滿意。這邊就說說個人體會的好壞：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2021.01.30 補充：外出也相當方便，只是桌面的空間就需要大一點&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;外接螢幕的讚&#34;&gt;外接螢幕的讚&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有雙螢幕很爽&lt;/li&gt;
&lt;li&gt;可以攜帶出門，到圖書館之類的地方也能有雙螢幕很爽&lt;/li&gt;
&lt;li&gt;雙螢幕在一邊 Coding 一邊 &lt;s&gt;抄Stackoverflow&lt;/s&gt; 查資料，或是像部落格一邊編輯一邊看結果的時候很爽&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其實整個說起來就是帶著筆電也能雙螢幕的那種爽感，一旦用過雙螢幕，感受到不用一直切換視窗的流暢感之後，就真的回不去啦。&lt;/p&gt;
&lt;h3 id=&#34;外接螢幕的沒那麼讚&#34;&gt;外接螢幕的沒那麼讚&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;加上保護套之後幾乎都要一公斤左右，不太能忽視&lt;/li&gt;
&lt;li&gt;出門在外還要接一堆線有時候不太美觀&lt;/li&gt;
&lt;li&gt;背包裏面又是筆電又是螢幕又是手機錢包的，身家財產哪！包包離開視線就會心神不寧&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;誰適合用外接螢幕&#34;&gt;誰適合用外接螢幕&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有出外工作習慣，例如跑去咖啡廳帥一天的朋友們&lt;/li&gt;
&lt;li&gt;像我一樣桌電筆電交替使用的朋友們&lt;/li&gt;
&lt;li&gt;只是想要出門用大螢幕打 Switch 的朋友們&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大概先這樣，推特也有字數限制呢。等有外出機會還是有什麼心得再來補充一下，先去享受新玩具了
這週也是今年最後一個連假了，各位開工愉快，咱們下周見&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/QDW7Xm9.webp&#34;
  alt=&#34;&#34;width=&#34;1706&#34; height=&#34;960&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充一下：我個人入手的型號是 MSI Optix MAG161V，算是相當輕便好看。線材接的位置也挺不錯的，螢幕看著也挺舒服。&lt;/p&gt;
&lt;p&gt;Google 一下也有挺多&lt;a href=&#34;https://www.kocpc.com.tw/archives/315455&#34;&gt;開箱文&lt;/a&gt;和介紹影片的，有興趣可以看看。我這真拍不出像樣的開箱文，太難了，我嘗試過了…。這邊就推薦一下外接螢幕這東西，有興趣的朋友再自己做功課吧～&lt;/p&gt;&lt;/blockquote&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (10): 單一職責原則</title>
      <link>https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle/</link>
      <pubDate>Fri, 02 Oct 2020 11:03:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VyyeaYz.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們在前面的 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;內聚和耦合&lt;/a&gt; 有提到過，內聚並不是無腦把相關的程式碼都封在一起就好了，也有分成健康的和不健康的。但我們要怎麼知道這個類別是否足夠健康呢？單一職責原則就是很好的檢驗方式，這篇就讓我們來紀錄一下。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#單一職責原則-single-responsibility-principle&#34;&gt;單一職責原則 (Single Responsibility Principle)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#走向單一職責&#34;&gt;走向單一職責&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#展現你的意圖&#34;&gt;展現你的意圖&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#同系列文章&#34;&gt;同系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;單一職責原則-single-responsibility-principle&#34;&gt;單一職責原則 (Single Responsibility Principle)&lt;/h2&gt;
&lt;p&gt;「單一職責」原則顧名思義，就是一個類別應該&lt;strong&gt;只負責一個職責&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但是這樣太過籠統了，「職責」相當容易產生誤會，容易變成各說各話。&lt;s&gt;畢竟咱們工程師最愛戰定義了嘛。&lt;/s&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「你這類別不優，它有兩個職責！登入跟登出！」&lt;/p&gt;
&lt;p&gt;『沒有啦，我這個類別就是負責帳戶管理的啊』&lt;/p&gt;
&lt;p&gt;「&lt;a href=&#34;https://dailyview.tw/Daily/2019/11/07&#34;&gt;OSSO&lt;/a&gt;。乾脆你全部放一起，然後說是負責網站管理算了，呵」&lt;/p&gt;
&lt;p&gt;『……你存心來找碴的是不是？』&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;為了避免像這樣產生職場糾紛，我們需要先定義一下什麼是「職責」。經過前輩們的努力（解釋）之後，單一職責的定義就成了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;就一個類別而言，應該只有一個引起它變化的原因&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;另外，我也看過「一個類別應該只對一個角色負責」的說法，這兩者的核心概念是一樣的。&lt;/p&gt;
&lt;p&gt;這邊讓我們簡單舉個例子。如果在訂單管理的類別中有一個新增訂單的方法，在收到訂單之後，會依序處理訂單、並取出會員的聯絡資訊，再依靠聯絡資訊寄送通知信件給會員。但它的實作全靠自己來，如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;新增訂單()
{
   // 收到訂單
   /*
     一些訂單的商業邏輯
   */

   // 寫入訂單
   /*
     一些和資料庫連線寫入資料的處理
   */

   // 取得聯絡資訊
   /*
     一些連到資料表或服務拿會員資料的處理
   */

   // 寄送通知
   /*
     一些寄送信件的處理，如寄送者和寄送方式等等
   */
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這樣一路就是流水帳打完收工，這樣的一個函式參雜了一堆不相干的邏輯，可能動輒數百行，每一段都處理各種不同的工作，一看就很明顯違反單一職責原則。&lt;/p&gt;
&lt;p&gt;當訂單處理的商業邏輯、查詢會員資料的邏輯或是通知會員的方式有變更的時候，這個函式都會受到影響，也就是說這個函式同時對多個不同對象負責。這樣的類別或函式就是&lt;strong&gt;不穩定的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;遇到這種情況，我們可以將其&lt;strong&gt;拆分&lt;/strong&gt;。讓上帝的歸上帝，讓凱薩的歸凱薩。&lt;/p&gt;
&lt;p&gt;例如說會員的處理一律封裝回會員管理類別，我們再藉由會員管理類別去調用其方法取回資料；寄送信件也封裝到通知管理類別，不用去管用什麼方法通知的，我們只需要去要求其通知即可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;有些朋友可能會有疑問，這樣不就會和會員處理類別、通知管理類別之類的其他類別有了&lt;strong&gt;耦合&lt;/strong&gt;關係嗎？有這樣的疑問是很合理的，這也就是為什麼我們會需要&lt;strong&gt;介面&lt;/strong&gt;來讓類別之間不要直接彼此依賴，這部份我們在 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt; 有詳細介紹。&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;新增訂單(訂單)
{
    處理訂單商業邏輯(訂單)

    訂單資料存取服務.儲存訂單() // 可能由資料存取層或連線管理等該職責的地方去實現

    通知服務.寄送訂單通知(訂單.訂購人編號)
}

處理訂單商業邏輯(訂單)
{
   // 專注在處理商業邏輯，不用管其他事
}

// 其他的職責拆分出去給負責該工作的類別
通知服務 { 寄送訂單通知(編號); }
訂單資料存取服務 { 儲存訂單(); }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我們&lt;strong&gt;把工作交給負責該職責的類別去做，自己只需要關注在自己正在處理的職責即可&lt;/strong&gt;。聰明的朋友可能已經注意到了，這就是&lt;strong&gt;封裝&lt;/strong&gt;的體現。封裝得夠舒服，我們就能舒服地處理自己的事情就好，這就是分工合作的偉大呀。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2022.01.23 補充：&lt;/p&gt;
&lt;p&gt;前面提到的「職責」的部分，雖然我們前面提過了是「引起變化的原因」，但可能還是太過模糊，&lt;s&gt;畢竟咱們工程師真的最愛戰定義了嘛。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;今天在群組裡有前輩分享了講解單一職責原則的影片：&lt;a href=&#34;https://youtu.be/e0UOuQ_lCUY?t=1073&#34;&gt;Fred 聊聊 SOLID 設計原則&lt;/a&gt;，其中單一職責的部份，將前述的「引起變化」從&lt;strong&gt;業務需求變更&lt;/strong&gt;的方式切入&lt;/p&gt;
&lt;p&gt;影片中用實例來說明什麼時候該切分職責和其重點，例如 &lt;strong&gt;「業務耦合造成的問題就是職責不明確」、「不要讓類別去碰它不該做的事情等等」&lt;/strong&gt; ，我個人覺得非常不錯，推薦給想更了解單一職責原則或 SOLID 的朋友&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;雖然文章中的例子相對簡單。但有一個部份我個人覺得要特別注意：單一職責當然也可以用在函式上。甚至資料表或任何需要管理、分類抽象事物的東西上。&lt;/p&gt;
&lt;p&gt;有些朋友可能跟我前陣子一樣，覺得函式就是用來消除重複的程式碼，直到我看了 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10206839&#34;&gt;可不可以不要寫糙 code&lt;/a&gt; 和一句「難道只有重複才需要做成 Function 嗎？」才明白：函式真正的工作其實是封裝邏輯。&lt;/p&gt;
&lt;p&gt;既然是封裝邏輯這種抽象的東西，必然也會有其職責，自然也得好好注意單一職責囉。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;走向單一職責&#34;&gt;走向單一職責&lt;/h2&gt;
&lt;p&gt;我們可以從上面的差別重新思考，遵守與不遵守單一職責原則會有哪些顯著的差異。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;當我們並未遵守單一職責原則時&lt;/strong&gt;，同個類別裡面充斥著不同工作的處理邏輯。也就是不健康的內聚：完全不夠聚，就只是盤散沙。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;容易產生&lt;strong&gt;意外的重複&lt;/strong&gt;。每個類別每個方法都自己去查詢會員資料，當查詢會員資料的方式或規則有變更的時候，影響範圍就會非常大，同樣的事情有一大堆地方要改，還得要先全部找出來，想到就頭痛。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同時我們在修改時也&lt;strong&gt;無法界定邊界&lt;/strong&gt;，無法確定這次修改影響到的範圍，我們並不知道這些放在一起的東西，或是同一段做的所有事之間&lt;strong&gt;是否會相互影響&lt;/strong&gt;，這將導致每次修改的時候都在挑戰我們自己的心臟負荷量，讓維護變成試膽大會，類別變成危樓改建。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;承上，我們為了要確保修改沒有問題，我們必須&lt;strong&gt;大量閱讀不相關的程式碼&lt;/strong&gt;，無形中造成開發負擔，降低開發效率。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你曾經有閱讀別人的程式碼，卻始終看不懂這東西到底在幹嘛，每分鐘髒話數筆直上升的經驗。答應我，&lt;strong&gt;我們不要讓別人經歷相同的悲劇&lt;/strong&gt;，我們要斬斷仇恨的鎖鏈。我們，今天就開始走向單一職責。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;當我們終於選擇單一職責&lt;/strong&gt;，我們的類別才能真的擁有健康的高內聚。以上的這些問題，也都變成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;每個類別都專注在自己職責上，需要這個功能的其他類別就能來使用。大大提高了程式碼的&lt;strong&gt;重複使用&lt;/strong&gt;程度，同時也&lt;strong&gt;降低了程式碼的重複性&lt;/strong&gt;。並且因為類別內都是朝同樣職責前進的成員，彼此關聯性相當高，因此也&lt;strong&gt;提高了內聚&lt;/strong&gt;。這兩點讓我們能迴避掉「要改的地方太多了，就改天吧」的悲傷結局。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同時，當我們要修改時，只需要找到負責的類別修改。因為已經把不屬於職責的工作交給其他類別了，達到了封裝和隔離，所以我們就能輕鬆看出修改的區域和邏輯，並較少地被不相干的東西影響、馬上掌握修改的目標和影響範圍，使得架構和類別&lt;strong&gt;更容易管理&lt;/strong&gt;。也就是說，單一職責可以達到&lt;strong&gt;降低耦合&lt;/strong&gt;的效果。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;展現你的意圖&#34;&gt;展現你的意圖&lt;/h2&gt;
&lt;p&gt;單一職責讓我想到前陣子看的&lt;a href=&#34;https://igouist.github.io/post/2020/09/start-with-why&#34;&gt;《先問為什麼》&lt;/a&gt;中的芹菜測試：當你在超市結帳時，手上拿著巧克力、豆漿、餅乾跟芹菜，沒有人看得出來你到底要幹嘛。&lt;/p&gt;
&lt;p&gt;寫程式也是如此，&lt;strong&gt;如果你的類別或方法裡什麼都要，彼此間又甚無關連，那就沒人看得懂這到底是幹嘛的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果團隊的其他人不能瞭解這個類別的職責，那後續協助修改的時候就會沒辦法把相同工作的程式碼歸類在一起，甚至難以修改，做起事綁手綁腳，新增個方法都會陷入混亂。整個架構就會開始腐敗。這也就是為什麼我們需要保持程式碼的可讀性，並且盡力實踐單一職責。&lt;/p&gt;
&lt;p&gt;如同我在先問為什麼文中所引用的「你的一言一行，都要能證明你的信念」。在這裡，你的類別、方法，甚至是程式碼中的每一區塊，都必須要能夠&lt;strong&gt;展現你的意圖&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此，單一職責不只能用來檢驗類別。從一整個服務，到單一個函式，都可以用它的意圖來問問自己。這一段是否只有一個職責？&lt;strong&gt;是否只有一個原因造成改變？職責是否清晰？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;當然，從模組到函式每一層級的抽象概念是不一樣的，模組有模組關注的點，函式有函式關注的點，其規模有所差異，請不要用函式的職責大小去要求整個類別，我個人覺得這中間的差異還是挺吃經驗的，但不去嘗試思考，就沒得經驗可說嘛。這邊還是鼓勵大家多多利用單一職責去檢驗任何片段的程式碼。&lt;/p&gt;
&lt;p&gt;當我們利用單一職責原則去檢驗，或是思考方向的時候，如果&lt;strong&gt;列得出兩項以上的變更原因，且這些原因彼此關聯很薄弱的時候，就是警訊&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;反過來說，即使有兩個原因引起變化，但這些原因之間的關聯很強，例如總是一起變化，那其實就不必分離，或是可以暫緩分離，避免&lt;strong&gt;過度設計&lt;/strong&gt;所引起的&lt;strong&gt;不必要的複雜性&lt;/strong&gt;。（白話文來說就是走火入魔）&lt;/p&gt;
&lt;p&gt;如果能做到撰寫功能當下，或是重構的時候不斷自我檢驗，那寫出來的程式碼品質相信也能展現出一定的水準了吧！共勉之。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文整理時主要參考了這兩篇，寫得相當不錯，想更瞭解的朋友可以參考一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/12/blog-post.html&#34;&gt;再談物件導向設計原則: 單一職責原則，定義、解析與實踐 - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jyt0532.com/2020/03/18/srp/&#34;&gt;深入淺出單一職責原則 Single Responsibility Principle - jyt0532’s Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;看到這篇覺得很不錯，從另一個角度切入單一職責，回來補充給各位朋友：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ruddyblog.wordpress.com/2022/11/25/%e5%b7%a5%e7%a8%8b%e5%b8%ab%e7%9a%84%e7%b0%a1%e5%96%ae%e8%a7%80%e5%bf%b5/&#34;&gt;工程師的簡單觀念 – Ruddy Lee 分享空間 (wordpress.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/12/blog-post.html&#34;&gt;再談物件導向設計原則: 單一職責原則，定義、解析與實踐 - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10191955&#34;&gt;SOLID 之 單一職責原則（Single responsibility principle）- Miles - iT邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jyt0532.com/2020/03/18/srp/&#34;&gt;深入淺出單一職責原則 Single Responsibility Principle - jyt0532&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/e0UOuQ_lCUY?t=1073&#34;&gt;Fred 聊聊 SOLID 設計原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-1_single-responsibility-principlesrp-%E5%96%AE%E4%B8%80%E8%81%B7%E8%B2%AC/&#34;&gt;Object Oriented 物件導向設計原則 SOLID-1:Single Responsibility Principle(SRP) 單一職責 - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@n26074273/solid-i-%E5%96%AE%E4%B8%80%E8%81%B7%E8%B2%AC%E5%8E%9F%E5%89%87-single-responsibility-principle-11e30ece0778&#34;&gt;SOLID-I 單一職責原則(Single Responsibility Principle) - 黃子源 - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789866761799&#34;&gt;《大話設計模式》&lt;/a&gt; Ch3. 拍攝UFO －－單一職責原則&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789864342945&#34;&gt;《無瑕的程式碼：整潔的軟體設計與架構篇》&lt;/a&gt; Ch.7 SRP －－單一職責原則&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789864342099&#34;&gt;《無瑕的程式碼：物件導向原則、設計模式與C#實踐》&lt;/a&gt; Ch.8 SRP －－單一職責原則&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 元組 (Tuple)</title>
      <link>https://igouist.github.io/post/2020/09/csharp-trulp/</link>
      <pubDate>Sun, 27 Sep 2020 23:56:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/09/csharp-trulp/</guid>
      <description>&lt;p&gt;因為隔壁介紹原則的部分有點卡住了，所以這週來紀錄一下挺常用到的方便東西：&lt;strong&gt;Tuple&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這篇的 Tuple 指的是 C# 7.0 後提供的 &lt;strong&gt;ValueTuple&lt;/strong&gt; 和相關語法，舊版得用 &lt;code&gt;Tuple.Create&lt;/code&gt; 建立，成員的名稱也只能使用 Item1, Item2&amp;hellip;，實用性並不是很高。但新 Tuple 出現後，方便程度大大提升，這邊就稍作紀錄一下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：此處使用的 &lt;code&gt;Dump&lt;/code&gt; 是 Linqpad 提供的輸出方法，把它當成 Print 就行了。&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; student = (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;王小明&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;student.Item1.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;student.Item2.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 王小明&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;student.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到 Tuple 的建立相當簡單，只需要用小括號 &lt;code&gt;()&lt;/code&gt; 括選起來即可。建立後的內容就會像這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/H0wJglN.webp&#34;
  alt=&#34;&#34;width=&#34;182&#34; height=&#34;84&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;但這樣使用就和之前一樣，取出來時只能拿 Item1, Item2，放個幾天根本就不記得 Item1 裡面是啥東西了。這時我們就可以&lt;strong&gt;替成員們取名字&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ID, &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name) student = (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;王小明&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;student.ID.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;student.Name.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 王小明&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此使用的時候就和一般操作物件的習慣沒有差別，也增加了可讀性。&lt;/p&gt;
&lt;p&gt;到這裡可能感覺只是個方便的變數打包小工具，但其真正順手的地方就在於作為&lt;strong&gt;回傳值&lt;/strong&gt;的時候。&lt;/p&gt;
&lt;p&gt;過去要一次回傳多個值，除了使用 Ref 等方式以外，就只能乖乖做一個類別來裝，但有時候傳的東西又相當簡單，實在不太願意就此建立一個類別，又或是事情已經無法挽回，專案虛胖了一堆&lt;strong&gt;米蟲類別&lt;/strong&gt;。有了 Tuple 之後，面對這種情況就可以&lt;strong&gt;直接使用 Tuple 解決&lt;/strong&gt;，並且替成員們命名之後，既簡便又好讀！&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 回傳型別使用 Tuple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; IsSuccess, &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Message) DoSomeThing()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 做了一堆事情&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;操作成功&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = DoSomeThing();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.IsSuccess.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	result.Message.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 操作成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而當我們作為回傳值使用時，也可以直接原地解封：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 回傳型別使用 Tuple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; IsSuccess, &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Message) DoSomeThing()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 做了一堆事情&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;操作成功&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 直接拆開賦值給多個變數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; (isSuccess, message) = DoSomeThing();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	isSuccess.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	message.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 操作成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;作為一個簡易臨時型別挺方便的，但要注意不要用過頭了。兩三個成員還算方便，如果有七八個甚至十來個成員，請還是乖乖做成類別吧！&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.huanlintalk.com/2017/04/c-7-tuple-syntax.html&#34;&gt;C# 7 新增的 Tuple 語法 - Huanlin 學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2018/10/01/135755&#34;&gt;[料理佳餚] 用 ValueTuple 解放雞肋類別 - 軟體主廚的程式料理廚房&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (9): SOLID</title>
      <link>https://igouist.github.io/post/2020/09/oo-9-solid/</link>
      <pubDate>Sun, 20 Sep 2020 13:51:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/09/oo-9-solid/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/U7iWMT9.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;終於進入了原則篇，接下來的幾篇我們會介紹幾個物件導向的原則（基本上就是指 SOLID 原則）。因此這篇就讓我 &lt;s&gt;水一下&lt;/s&gt; 當成後半段的目錄，方便之後可以把相關的部分整理進來。&lt;/p&gt;
&lt;h2 id=&#34;為什麼我們需要這些原則&#34;&gt;為什麼我們需要這些原則？&lt;/h2&gt;
&lt;p&gt;我們在前面的章節已經說明了一些物件導向的特性，例如繼承和多型等等。然而我們並沒有討論到怎麼運用、或是怎樣設計才能算是更好的、更優雅的、更符合物件導向精神的；我們並沒有提到一個評估的標準，或是指引一個更好的方向。&lt;/p&gt;
&lt;p&gt;然而，混亂的使用物件導向對整個專案的毀滅性甚至比乾脆不使用物件導向還高。&lt;/p&gt;
&lt;p&gt;這些特性使用起來很簡單，大多數語言只需要一個符號或標示就能完成繼承，把一堆東西全部塞在一起就可以說我在封裝。但怎麼使用得好，又該什麼時候使用呢？這就是難的地方吧。&lt;/p&gt;
&lt;p&gt;例如說濫用繼承，或是封裝時完全不隱藏複雜度一路 Puuuuublic 到底，又或者是類別之間過於相互依賴，全部耦合成一團等等。如果隨便地使用物件導向的各項特性，就會讓整個架構變得僵化、脆弱、危險、充滿臭味。&lt;/p&gt;
&lt;p&gt;更可怕的是，這個發臭的過程是每一次設計、每一次修改都會有所影響，所謂「持續發生，腐敗成真」，&lt;strong&gt;隨著物件導向的亂用、誤用、無腦用，軟體就會逐漸腐化&lt;/strong&gt;。一組腐化的軟體可能會有以下特徵：大量的依賴使得修改變得困難、修改後看似不相干的各個地方發生問題、或是修改時沒辦法依循原本的設計、到處出現不必要的複雜性和不必要的重複，模組也變得難以理解等等。&lt;/p&gt;
&lt;p&gt;阻止程式碼的腐化、追求更好的架構和設計、寫出更好的代碼，當然是我輩所追求的目標。儘管面對的可能是不同的問題和不同的環境，那些優質、穩固、具有&lt;strong&gt;反脆弱&lt;/strong&gt;特質的程式碼也必然會有些共通之處。例如說：需要具有&lt;strong&gt;面對改變&lt;/strong&gt;的能力、具有&lt;strong&gt;方便管理&lt;/strong&gt;的能力、具有&lt;strong&gt;隱藏複雜性&lt;/strong&gt;的能力。&lt;/p&gt;
&lt;p&gt;因此，大前輩們整理並提出了一些可以致力的方向，也就是所謂的「&lt;strong&gt;原則&lt;/strong&gt;」。如同心法、教義一般，只要實作的同時將其牢記在心，就能讓我們作為一些行動的準則和依據。&lt;/p&gt;
&lt;p&gt;所謂練拳不練功，到老一場空。我們可不能看了招式就無腦用，先讓我們看一下這些 SOLID 原則的目標是什麼。&lt;/p&gt;
&lt;p&gt;在 &lt;a href=&#34;https://www.books.com.tw/products/0010786994&#34;&gt;Clean Architecture&lt;/a&gt; 裡是這樣說明的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這些原則的目標是建立中層級的軟體結構，這樣的結構包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能容忍變化&lt;/li&gt;
&lt;li&gt;容易理解&lt;/li&gt;
&lt;li&gt;在許多軟體系統中能夠使用的元件的基礎&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「中層級」是指這些原則是程式設計師在模組層級工作時應用的原則。它們應用在程式碼層級之上，並且有助於定義模組和元件內使用的軟體結構類型。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;我們應用這些原則的場景，應該是在所謂的「中層級」發生。也就是並非小到一行程式碼，也並非是一整個專案，而是其中的各個「&lt;strong&gt;模組&lt;/strong&gt;」。不論是類別、介面又或是其他名稱的任何東西，凡是具有函式或資料的中層級，我們就可以運用這些原則來處理。&lt;/p&gt;
&lt;p&gt;而我們之所以要用這些原則，就是為了達到 &lt;strong&gt;能容忍變化&lt;/strong&gt;、&lt;strong&gt;容易理解&lt;/strong&gt;、&lt;strong&gt;能讓模組和元件使用&lt;/strong&gt; 這些目標。&lt;/p&gt;
&lt;p&gt;這些目標可以當作一個良好的程式碼模組該有的特徵。你的類別必須能容忍變化，必須具備可擴展性和可修改性，畢竟&lt;strong&gt;軟體的需求大多時候都是擴展跟修改&lt;/strong&gt;。更進一步說，功能和彈性之間甚至應該先選擇彈性，畢竟為了功能犧牲彈性的話，一但面對變化，整個程式就碎了；但優先選擇彈性的話，至少你還有機會能把它修改得更符合功能，所以對這些原則而言，能容忍變化是相當重要的。甚至，&lt;strong&gt;整個 SOLID 就是面對變化的作戰策略&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而容易理解就更重要了。Clean Code 裡有提過，&lt;strong&gt;閱讀程式碼和實際開工打字的時間大約是佔 10 : 1&lt;/strong&gt;，因此是否容易理解，是否乾淨好懂就是相當重要的一環。看得快，就寫得快；寫得越快，心越慢。&lt;/p&gt;
&lt;p&gt;如同我們在首篇所說，物件導向就是在替我們把概念抽象化，而這抽象過程所使用的這些特性，就是為了減少複雜性、提高可理解度而存在的。因此，一組優良的程式碼，容易理解是絕對必要的。&lt;/p&gt;
&lt;p&gt;另外關於為什麼我們需要這些原則，我個人推薦可以先閱讀這幾篇，對我個人來說很有收穫：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/10/solid.html&#34;&gt;淺談物件導向 SOLID 原則對工程師的好處與如何影響能力 - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/10/solid-why-solid.html&#34;&gt;再談 SOLID 原則，Why SOLID? - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://teddy-chen-tw.blogspot.com/2014/04/solid.html&#34;&gt;SOLID：五則皆變 - 搞笑談軟工&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;說那麼多所以到底有哪些原則&#34;&gt;說那麼多，所以到底有哪些原則？&lt;/h2&gt;
&lt;p&gt;現在我們已經了解到，因為軟體會逐漸腐化，所以我們要找出原則；這些原則的目標，就在於設計出可變化可理解的優質模組。現在，是時候公布我們 SOLID 五大天王的名諱了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;單一職責原則 &lt;strong&gt;S&lt;/strong&gt;ingle Responsibility Principle (SRP)&lt;/li&gt;
&lt;li&gt;開放封閉原則 &lt;strong&gt;O&lt;/strong&gt;pen-Closed Principle (OCP)&lt;/li&gt;
&lt;li&gt;里氏替換原則 &lt;strong&gt;L&lt;/strong&gt;iskov Substitution Principle (LSP)&lt;/li&gt;
&lt;li&gt;介面隔離原則 &lt;strong&gt;I&lt;/strong&gt;nterface Segregation Principle (ISP)&lt;/li&gt;
&lt;li&gt;依賴反轉原則 &lt;strong&gt;D&lt;/strong&gt;ependency Inversion Principle (DIP)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;註：大多時候 L 的位置也會多一個 Law of Demeter 迪米特法則（= Least Knowledge Principle 最少知識原則），畢竟也挺重要的，而且四大天王都有五個人了，五大原則有六個也是剛剛好。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;而它們的首字合起來就是 &lt;strong&gt;&lt;code&gt;SOLID&lt;/code&gt;&lt;/strong&gt;，表達出那種穩固的、可靠的感覺！順便一提，順序沒有任何關係，會排成 SOLID 純粹只是作者朋友當時覺得這樣比較好記。&lt;/p&gt;
&lt;p&gt;那麼從&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;下一篇&lt;/a&gt;開始，我們就按照 SOLID 的順序，從單一職責開始介紹。我們下次見～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/10/solid.html&#34;&gt;淺談物件導向 SOLID 原則對工程師的好處與如何影響能力 - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wadehuanglearning.blogspot.com/2019/10/solid-why-solid.html&#34;&gt;再談 SOLID 原則，Why SOLID? - WadeHuang的學習迷航記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://teddy-chen-tw.blogspot.com/2014/04/solid.html&#34;&gt;SOLID：五則皆變 - 搞笑談軟工&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ChunYeung/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E7%9B%AE%E9%8C%84-b33fdfc983ca&#34;&gt;使人瘋狂的 SOLID 原則：目錄 - YC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@f40507777/%E6%88%91%E8%A9%B2%E5%AD%B8%E6%9C%83solid%E5%97%8E-4e73887c9156&#34;&gt;我該學會SOLID嗎? - Finn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.ycc.idv.tw/introduction-object-oriented-programming_3.html&#34;&gt;物件導向武功秘笈（3）：內功篇 — 物件導向指導原則SOLID - YC Chen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/e0UOuQ_lCUY&#34;&gt;Fred 聊聊 SOLID 設計原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E6%89%8B%E5%AF%AB%E7%AD%86%E8%A8%98/clean-code-b45a89ea8c66&#34;&gt;第 1 章 無瑕的程式碼 | Clean Code - 手寫筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.books.com.tw/products/0010786994&#34;&gt;《無瑕的程式碼：整潔的軟體設計與架構篇》&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (8): 內聚、耦合</title>
      <link>https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling/</link>
      <pubDate>Sun, 13 Sep 2020 23:56:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/cgxW9yZ.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;做為前後段落的分水嶺，這篇文章我將紀錄一下 &lt;strong&gt;「內聚」(Cohesion)&lt;/strong&gt; 和 &lt;strong&gt;「耦合」(Coupling)&lt;/strong&gt;，這兩者是評估一個類別或元件的重要概念。&lt;/p&gt;
&lt;p&gt;在實務上，為了提升擴展性，降低維護成本等因素，我們對於單個類別或元件，會有著 &lt;strong&gt;「低耦合」&lt;/strong&gt; 及 &lt;strong&gt;「高內聚」&lt;/strong&gt; 的期待。例如我們在 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt; 中，我們就有提到封裝的好壞相當重要，其中也包含了「提高類別內的內聚性，降低對外的耦合性」。那麼，到底什麼是內聚，什麼又是耦合呢？&lt;/p&gt;
&lt;h2 id=&#34;內聚&#34;&gt;內聚&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「把需要的程式和資料都包裝在同一個模組內，使得該模組能夠做為一個單獨的個體執行」&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;白話一點說，就是就是把用到的東西都打包到一處，該有的自己都有了，所以即使單獨一個人也能完成工作的能力、可以自己 Carry 整場不用看豬隊友臉色的能力。越能自己單幹，越不需要依賴其他類別的時候，內聚力也就越高。&lt;/p&gt;
&lt;p&gt;也就是說：如果你的類別什麼都要依賴其他類別，像小嬰兒一樣需要呵護照顧，那內聚力就很低。反之，如果像野外求生大師，啥都靠自己，那內聚力就超高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;內聚代表的是該模組的獨立性，當這個模組可以獨力完成工作，就代表我們能夠重複使用它，且不需要擔心影響到其他模組。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;並且也基於這點，我們不用擔心變動這個模組時需要先處理其他的模組，因為這個工作所需的都包含在模組內了，這樣就可以&lt;strong&gt;單獨修改&lt;/strong&gt;該模組，減少維護成本。&lt;/p&gt;
&lt;p&gt;例如你的筆已經包含了所有寫字工具的條件，具有墨水跟筆芯等等，可以只使用筆就完成寫字這個工作。那麼我們就可以隨身帶著，在任何需要的時候重複使用它，而不用擔心我們會不會漏了什麼必要零件沒有帶出門。同時，如果我們需要換筆芯或墨水，我們也知道要更換的部份就在筆裡面，不需要去找鉛筆盒中別的地方。&lt;/p&gt;
&lt;p&gt;而我們在物件導向的世界中，是將不同的邏輯和功能，封裝成不同的物件，藉由這些物件的互動來構築我們抽象化的世界和想法。為了隱藏這些物件內部的複雜性，同時又保持物件的整體性，讓物件能真的符合我們概念中的「一個」物件，那麼&lt;strong&gt;追求高內聚就是必然的&lt;/strong&gt;。內聚，是物件的一種美德。&lt;/p&gt;
&lt;p&gt;然而，&lt;strong&gt;盲目地追求高內聚是很危險的&lt;/strong&gt;。只要你希望，當然可以寫出一個超級內聚的類別，但這代表什麼呢？&lt;/p&gt;
&lt;p&gt;為了提高內聚，把所有相關的東西都一股腦塞進同一個類別，越塞越多越塞越多，沒那麼相關的東西也硬塞在一起。從模組變成義大利麵，再從義大利麵變成大補帖，最後終於變成&lt;a href=&#34;https://en.wikipedia.org/wiki/God_object&#34;&gt;神&lt;/a&gt;。這樣實際上根本就不內聚，類別裡面就是一堆散沙，&lt;strong&gt;功能一大堆動輒數千行，改個 Bug 先看三千行程式碼&lt;/strong&gt;，維護者莫不痛哭流涕…&lt;/p&gt;
&lt;p&gt;又或是為了避免上面的狀況，限制了功能範圍。但卻又為了能獨立作業，為了不依賴別人，硬是把別的地方已經有的功能複製一份過來，用到的東西都複製複製複製進來，人人都有一份。最後遇到修改時，&lt;strong&gt;要改這又要改那，等著改的地方遍地開花&lt;/strong&gt;，維護成本暴增，維護者再度痛哭流涕…&lt;/p&gt;
&lt;p&gt;到這邊應該能瞭解到&lt;strong&gt;完全內聚是不可能也不應該的&lt;/strong&gt;，過於執著就會走火入魔。那麼怎樣的內聚算是剛剛好呢？或是說，一個良好的高內聚？這就牽涉到這段程式碼的&lt;strong&gt;意圖&lt;/strong&gt;了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;良好的內聚應該只關注在一件事情上，並適時地將不屬於自身職責的工作交給別人&lt;/strong&gt;，達到所謂「&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10203659&#34;&gt;該內聚而內聚，該耦合而耦合&lt;/a&gt;」。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;所謂「只關注一件事情」、「不屬於自身職責」云云，我們在之後的 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;單一職責原則&lt;/a&gt; 會更進一步地說明。且先按下不表。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;為了減少重複程式碼，和降低維護的困難，不管怎樣互動都是不可避免的。那既然我們的物件多多少少都得依賴別人，就不能不提到耦合了。&lt;/p&gt;
&lt;h2 id=&#34;耦合&#34;&gt;耦合&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「如果模組和另一個模組有關聯，那這兩者之間就耦合」&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;耦合的定義就是這麼寬廣。不管是接收另一個物件傳入的值，或者是共用同個全域變數，更何況我中有你你中有我，都是耦合。&lt;/p&gt;
&lt;p&gt;當兩者之間的關聯越緊密，越無法分離，其耦合度就越高。例如說&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;繼承&lt;/a&gt;關係就是強耦合的代表。&lt;/p&gt;
&lt;p&gt;當我們的目標放在減少重複的程式碼時，就會有多個模組共用同一段程式碼的情形發生，也會造成這些模組和這段重複使用的程式碼彼此耦合。&lt;/p&gt;
&lt;p&gt;那當我們為了其中一個使用者修改了這段程式碼，就會連帶影響其他用到的地方。變成&lt;strong&gt;改了這裡壞那裡，修了那裡壞這裡&lt;/strong&gt;的詭異情況。這也就是我們追求降低耦合的最大原因。&lt;/p&gt;
&lt;p&gt;彼此關聯就會彼此牽連，因此我們要讓彼此之間保持一個&lt;strong&gt;舒適的距離&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;注意，是舒適的距離，而不是不相往來，從這點來看，&lt;strong&gt;健康的內聚就是健康的耦合&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;內聚與耦合&#34;&gt;內聚與耦合&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;內聚是模組的獨立性，耦合則是模組的關聯性&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;「低內聚高耦合」的組合，牽一髮動全身，改個一行程式碼動輒就是大規模傷害，我們甚至不能切分模組，完全和物件的精神背道而馳，這是萬不能接受的。&lt;/p&gt;
&lt;p&gt;「高內聚低耦合」則是大家所追求的目標。為了讓每個物件各自獨立又能彼此互動，從物件導向中&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;封裝&lt;/a&gt;的角度出發，這個方向絕對是正確的。&lt;/p&gt;
&lt;p&gt;但所謂過猶不及，若是太過火變成「超高內聚無耦合」，又會變成可怕的 All in one 融合怪物或是 Ctrl C VVVVV 的複製大軍……&lt;/p&gt;
&lt;p&gt;不健康的內聚和不健康的耦合都是問題，內聚和耦合這兩者就像天秤的兩端，我們的目標就是找到那個合適的平衡點，也就是&lt;strong&gt;健康的高內聚低耦合&lt;/strong&gt;才是我們所追求的。&lt;/p&gt;
&lt;p&gt;同時也可以注意到內聚和耦合會發生的問題，例如修改時影響其他物件導致壞一整片，又或是修改時太多地方要改成本過高，總是圍繞在擴展和維護，基本上就是&lt;strong&gt;面對改變時會發生的問題&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;就像基因的優劣在於適應環境並生存下去的能力，程式碼也是如此。為了協助我們追求健康的高內聚低耦合目標，也&lt;strong&gt;為了讓我們面對改變（遭遇災難）時有個方針，因此才有了一些原則&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;就像我們前面敘述內聚時一直迴避的這些問題：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;怎樣才是適合的內聚？怎樣才是健康的耦合？&lt;/li&gt;
&lt;li&gt;如果說過高的內聚會塞太多功能或複製重複功能而變成怪物，過低的內聚則會四處拈花惹草，那我們要怎麼知道這個類別或元件的功能範圍剛剛好？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些問題的參考準則，就在於我們之後要介紹的「&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;單一職責原則&lt;/a&gt;」！&lt;/p&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;過了兩個月再度接續這個系列，一回來就是一篇碎碎念充當預告片，總之就先交代一下內聚和耦合的概念。&lt;/p&gt;
&lt;p&gt;但要真的達到健康的內聚和健康的耦合，不造神、不亂依賴、物件裡面高內聚、物件彼此低耦合，就必須要有一些原則。&lt;/p&gt;
&lt;p&gt;所以&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;下回&lt;/a&gt;開始就要進入物件導向五大原則的段落了，那麼，我們下次見！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：內聚跟耦合算是相當重要又基礎的觀念，我個人也還在摸索，只聞其聲不見其影。想要更了解這兩個概念的朋友，可以將參考資料的文章都看過一遍，我個人覺得頗有幫助，尤其是&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10203659&#34;&gt;實務上的高內聚與低耦合&lt;/a&gt;、&lt;a href=&#34;http://teddy-chen-tw.blogspot.com/2011/12/1.html&#34;&gt;搞笑談軟工&lt;/a&gt;兩篇，值得特別推薦。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10203659&#34;&gt;實務上的高內聚與低耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://teddy-chen-tw.blogspot.com/2011/12/1.html&#34;&gt;亂談軟體設計（1）：Cohesion and Coupling - 搞笑談軟工&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10191761&#34;&gt;斷開鎖鏈! 低耦合、高內聚&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10206839&#34;&gt;如何寫高品質 function (內聚性篇)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-5_%E5%85%A7%E8%81%9Acohesion%E8%80%A6%E5%90%88coupling/&#34;&gt;Object Oriented物件導向-5:內聚(Cohesion)、耦合(Coupling) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/e0UOuQ_lCUY&#34;&gt;Fred 聊聊 SOLID 設計原則&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>讀《先問為什麼》</title>
      <link>https://igouist.github.io/post/2020/09/start-with-why/</link>
      <pubDate>Sun, 06 Sep 2020 10:02:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/09/start-with-why/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mC6ySzk.webp&#34;
  alt=&#34;&#34;width=&#34;353&#34; height=&#34;501&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你樂於接納新事物，希望成功能持久，也相信自己的成功需要別人的幫助&lt;/p&gt;
&lt;p&gt;我向你提出一個挑戰 ——&lt;/p&gt;
&lt;p&gt;從今天起，做任何事情之前，請先問自己「為什麼」。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這本書的中文副標很好地點出了本書的重點：顛覆慣性思考的&lt;strong&gt;黃金圈理論&lt;/strong&gt;，啟動你的&lt;strong&gt;感召領導力&lt;/strong&gt;。大多篇幅用在舉例以及逐步說明何謂感召，以及黃金圈。&lt;/p&gt;
&lt;p&gt;本書的想法和一些例子，尤其是書中最重要的黃金圈理論，在作者上 TED 的影片「&lt;a href=&#34;https://www.ted.com/talks/simon_sinek_how_great_leaders_inspire_action?language=zh-tw&#34;&gt;偉大的領袖如何鼓動行為&lt;/a&gt;」都有說明，有興趣的朋友可以直接看演講影片，足夠掌握到黃金圈理論的核心。&lt;/p&gt;
&lt;iframe src=&#34;https://embed.ted.com/talks/lang/zh-tw/simon_sinek_how_great_leaders_inspire_action&#34; width=&#34;100%&#34; height=&#34;480&#34; frameborder=&#34;0&#34; scrolling=&#34;no&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;（偉大的領袖如何鼓動行為 - TED）&lt;/p&gt;
&lt;p&gt;作者認為影響人類行為的方法有兩種：&lt;strong&gt;操弄&lt;/strong&gt;以及&lt;strong&gt;感召&lt;/strong&gt;，其中操弄是指使用手段或策略去影響他人，例如說談判、利誘、情緒勒索、削價競爭等等，所謂「人有所好，以好誘之無不取；人有所懼，以懼迫之無不納」就是操弄。&lt;/p&gt;
&lt;p&gt;同時操弄也是我們日常最常接觸到的方法，可說是無所不在，並且很容易達到目標，短期見效。但如果將目光放到建立長期關係，則操弄就不一定是個好方法：它能帶來短期的利益，但並不能帶來忠誠度和信任，過度依賴操控就像在懸崖邊跳舞，一失足就難以挽回。&lt;/p&gt;
&lt;p&gt;而感召，則是用激勵人心的方式去影響他人、促使別人展開行動。為了說明這些領導者如何能做到感召他人，作者提出了黃金圈理論。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WM8Js7d.webp&#34;
  alt=&#34;&#34;width=&#34;552&#34; height=&#34;538&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;從最外圈到最內圈分別為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;做什麼&lt;/strong&gt;：負責什麼工作、提供什麼商品等等。每個人都能說得出來。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;怎麼做&lt;/strong&gt;：流程、方法、技術。知道怎麼把事情做好，比做什麼更加抽象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;為什麼&lt;/strong&gt;：目的、使命、信念。真正的核心，但很少人能清楚說明。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我們能很清楚說出我們正在做什麼，有些時候也能說得出來該怎麼做，但很少人會提及為什麼要這樣做。這是因為大多數人傾向從較為清楚的點開始，而將模糊的事往後延，所以行為模式就會是從黃金圈由外往內。從具體的行為到模糊的原因。&lt;/p&gt;
&lt;p&gt;然而，能激勵熱情的做法卻不一樣，他們是由內往外的，他們會先問為什麼。作者針對這兩種的差異，給了一組電腦公司的銷售方式做為例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我們很會做電腦&lt;/li&gt;
&lt;li&gt;我們的電腦有最美的設計，不但使用簡單，也容易上手&lt;/li&gt;
&lt;li&gt;想要買一台嗎？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這就是先從最外圈做什麼開始的訴求，但這樣實在沒有什麼吸引力。然而，大多數的企業和廣告都採用這種方式行銷，先從「我們做的商品，這項商品和其他商品的不同處」開始，然而這並不能達到感召。接著，作者提出了蘋果的溝通模式來對比：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我們做的每件事，都是為了挑戰、改變現況，因為我們相信「不同凡想」的力量&lt;/li&gt;
&lt;li&gt;我們挑戰現況的方法，就是讓我們的產品有最美的設計，而且簡單好用&lt;/li&gt;
&lt;li&gt;剛好，我們做的就是最棒的電腦&lt;/li&gt;
&lt;li&gt;想要買一台嗎？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只是調整一下訴求的順序，並說明為什麼而已，感覺就會截然不同。&lt;/p&gt;
&lt;p&gt;這之間的差別在於，所謂設計、簡單、好用，只是你的理念具體化的表現。所有公司都能夠聘請厲害的設計師和工程師，所有公司也都各有優劣之分，多數人和企業認為有形的功能和價值會是致勝關鍵，不可否認產品和策略對於優勢息息相關，然而在這些背後，你為什麼而做，你所做的是否能夠表達出你的理念，這就會是關鍵的差別。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;吸引人們的，不是你做什麼，而是你為什麼做。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用優劣得失衡量而來的顧客，終究會因為優劣得失離開；但用理念吸引來的顧客，將會和你並肩作戰。這也就是開場時，作者將操弄和感召作為對比的原因，在這裡直接延伸成忠誠度，若只用手段和策略去鞏固顧客，這樣的關係危險且無法持久。但當你的目標和理念能和顧客產生共鳴時，顧客甚至能夠包容你產品的缺點，而以你產品的優點為傲。這是只講究手段、策略、怎麼做的人難以理解的。&lt;/p&gt;
&lt;p&gt;只執著在怎麼做的人，會用差異化的方式來試圖脫穎而出，也就是說服顧客自己和其他競爭者與眾不同。然而，大多數的產品對顧客來說都是所差無幾的，例如生活用品、衛生紙、牛奶，對顧客來說牛奶就是牛奶。並且，當我們只用怎麼做和做什麼兩個著眼點去進行這件事時，很容易陷入惡性循環。例如說削價競爭，用低價當做訴求，不斷削價的結果可想而知。&lt;/p&gt;
&lt;p&gt;真正最有效的差異，就在於你為什麼而做。&lt;/p&gt;
&lt;p&gt;「一台擁有5GB容量的MP3播放器」和「（為了）放一千首歌在你的口袋裡」中間的差別是顯而易見的。&lt;/p&gt;
&lt;p&gt;當然，知道了為什麼之後，還是得知道怎麼做。總不能都說要放一千首歌在你的口袋，然後連個撥放器都造不出來吧，因此知道為什麼的人，還是需要那些知道怎麼做的人。不是說想到好棒的點子就趕快找認識的工程師朋友去咖啡廳泡茶，而是先知道為什麼的好處在於，你能夠區分出選擇有哪些。&lt;/p&gt;
&lt;p&gt;當你的信念是為了改變世界，你就不會考慮削價競爭。當你的訴求是「讓每個人躺上我們的沙發都像個國王！」你就不會考慮用抽換劣質棉花的方式來降低成本。&lt;strong&gt;先知道為什麼之後，有助於讓你的怎麼做變得清晰&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而當你的黃金圈趨向一致、平衡的時候，你的為什麼足夠清晰、具有號召力，怎麼做和做什麼都能展現出你的信念的時候，自然就能吸引到那些和你志同道合的人們，創造忠誠度和歸屬感。所以，與其要求所謂「忠誠」、「誠信」這些結果（如果還必須特地要求這些，那應該也是相當危險了），不如把自己的「為什麼」表達出來，有紀律地根據「為什麼」去貫徹「怎麼做」，讓黃金圈完善，那麼自然就能達到這些效果。&lt;/p&gt;
&lt;p&gt;就像我們在&lt;a href=&#34;https://igouist.github.io/post/2020/06/darkhorse&#34;&gt;前一篇《黑馬思維》的心得&lt;/a&gt;所說，先知道微動力之後，就能據此選擇適合的策略，所以選擇並不難，符合自己就好。如果你能先找出為什麼，那麼你該怎麼做的選擇就會縮小到符合為什麼的範圍，如此一來也就簡單多了。再拿我喜歡的卡牌遊戲舉例，當所有人的牌組都一致時，&lt;strong&gt;對於核心概念的理解程度就會呈現在策略方針的差別，進而決定最後的高度&lt;/strong&gt;。因此，想要做出明智的選擇，就先從問為什麼開始。&lt;/p&gt;
&lt;p&gt;為了表達「為什麼」與「怎麼做」之間的關係，作者提出了&lt;strong&gt;芹菜測試&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;假設你去參加一個晚宴，忽然有朋友跑來告訴你，「你知道你的組織需要什麼嗎？M&amp;amp;M巧克力。不用M&amp;amp;M，根本就是有錢不去賺。」&lt;/p&gt;
&lt;p&gt;這時，另一個朋友又跑過來，說：「你知道自己該做什麼嗎？豆漿。研究顯示，現在每個人都在買豆漿。你應該賣豆漿才對。」&lt;/p&gt;
&lt;p&gt;當你站在雞尾酒桌旁邊的時候，另一位朋友又給你一個建議。「Oreo餅乾，」他說。「我們靠Oreo餅乾賺進了好幾百萬美元。你絕對要做Oreo餅乾。」&lt;/p&gt;
&lt;p&gt;這時又來了一個人，告訴你：「芹菜！你一定得做芹菜這門生意。」&lt;/p&gt;
&lt;p&gt;所有這些有成就的朋友給了你那麼多重要建議。有些人和你身處同一產業，有些人比你成功，有些人也給別人相同的建議，結果讓人受益匪淺。這時，你該怎麼辦？&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;當你跑去超市把上面提到的東西一股腦都買了，在排隊付錢時，所有人看著你手上的東西，根本不知道你相信什麼。你做的事情應該體現你的目標，但你手上什麼都有。&lt;/p&gt;
&lt;p&gt;但如果在你去超市之前，先確認好自己的為什麼。假設你的目標是為了健康，那你就只會拿著豆漿跟芹菜。而從結果來說，你在超市花的時間和金錢也比較少，每個人也都能感覺得到你的理念。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你的一言一行，都要能證明你的信念。為什麼的本質就是信念。怎麼做，則是你實踐信念的行為，而做什麼則是這些行為的結果。《先問，為什麼》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;作者在書中小心叮嚀不要走歪，所謂的走歪就是為什麼和怎麼做兩者漸行漸遠，手段開始不符理念，策略遠離目標的時候。由於怎麼做的操弄和策略通常是立即有效的，而為什麼的感召則是緩慢的、可能會停滯的。一旦鐵粉量飽和了、理念號召的做法陷入停滯時，多數人會不斷嘗試採取各種手段以求突破現況，這中間難免會為了立即的甜頭而走上了岔路。&lt;/p&gt;
&lt;p&gt;為了抓住所謂市場機會或是尋求突破等等，而使得「為什麼」和「怎麼做」脫鉤的時候，很可能造成原本的忠誠度下降，進而引發其他問題。長久以來就會變得危險，如同不斷追尋成長的殭屍，但&lt;a href=&#34;https://www.books.com.tw/products/0010825335&#34;&gt;世界上沒有所謂的『持續成長』&lt;/a&gt;，失去信念後盲目亂竄只是變成我們開頭舉例的那些公司的其中一員罷了。&lt;/p&gt;
&lt;p&gt;因此時刻檢驗自己的「為什麼」，自己的「怎麼做」和「做什麼」有沒有符合「為什麼」是相當重要的。&lt;/p&gt;
&lt;p&gt;換句話說，就是&lt;strong&gt;莫忘初衷&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;釐清自己的做什麼並非最困難的部分。真正難的是相信自己的直覺、堅持自己的願景、使命或信念。保持平衡與真誠才是最困難的部分。《先問，為什麼》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;最後，這本書的副標和內容似乎都展現出是一本管理學書籍的感覺，例如企業、忠誠度、領導力等等。但作者也說了：每個組織或團體，都是由一個人或一小群人來的。就像我們前一篇黑馬思維的心得提過的，你要把你個人當一個品牌來經營。這些道理應用在個人的生活和行為也是相當符合的。&lt;/p&gt;
&lt;p&gt;如同作者所說的：&lt;strong&gt;我們永遠只能從我們自己開始&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最後就放一下作者書中提到的工人故事，在 &lt;a href=&#34;http://www.evanlin.com/reading-why-first/&#34;&gt;[好書分享] 先問，為什麼？&lt;/a&gt; 看到之後就蠻喜歡的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;有一位哲學家到一個建築工地分別問三個正在砌築的工人說：「你在幹什麼？」&lt;/p&gt;
&lt;p&gt;第一個工人頭也不抬地說：「我在砌磚。」&lt;/p&gt;
&lt;p&gt;第二個工人抬了抬頭說：「我在砌一堵牆。」&lt;/p&gt;
&lt;p&gt;第三個工人熱情洋溢、滿懷憧憬地說：「我在建一座教堂！」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;願各位都能找到心中的教堂，找到自己為何而戰，找到「為什麼」，共勉之。&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀及參考資料&#34;&gt;延伸閱讀及參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.evanlin.com/reading-why-first/&#34;&gt;[好書分享] 先問，為什麼？顛覆慣性思考的黃金圈理論，啟動你的感召領導力 - KKDAI.GITHUB.IO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.guccidgi.com/2020/02/start-with-why/&#34;&gt;先問為什麼 如何徹底改變了我所有的行為? - 追日Gucci&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.inside.com.tw/article/13179-start-with-why&#34;&gt;【硬塞書摘】感召領導力：你要得到更多客戶，還是創造鐵粉？ - 張柏崧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;遺珠之憾&#34;&gt;遺珠之憾&lt;/h2&gt;
&lt;p&gt;作者在書中將黃金圈的三層，和大腦的結構對應起來，並將為什麼類比為我們的緣腦，掌控情感、信任、決策等等，同時將做什麼類比為我們的新皮質，掌控理性和分析。並且認為我們常有無法理性分析的直覺，並且常常證明是對的，作者將其和為什麼的信念連結在一起。對直覺和理性之間的互動有興趣的朋友，可以試著讀讀看&lt;a href=&#34;https://www.books.com.tw/products/0010780181&#34;&gt;《快思慢想》&lt;/a&gt;，或是看過網路上大大整理的&lt;a href=&#34;https://www.books.com.tw/products/0010780181&#34;&gt;系統一和系統二關係圖&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另外，書中還有一些段落並沒有節錄出來，例如用黃金圈和市場溝通，如何引爆趨勢（可以參見&lt;a href=&#34;https://wiki.mbalib.com/zh-tw/%E7%BD%97%E6%9D%B0%E6%96%AF%E7%9A%84%E5%88%9B%E6%96%B0%E6%89%A9%E6%95%A3%E6%A8%A1%E5%9E%8B&#34;&gt;創新擴散理論&lt;/a&gt;和&lt;a href=&#34;https://wiki.mbalib.com/zh-tw/%E7%97%85%E6%AF%92%E5%BC%8F%E8%90%A5%E9%94%80&#34;&gt;病毒行銷&lt;/a&gt;），以及關於「為什麼」的一些不錯的例子（西南航空、沃爾瑪、福特）等等。如果有興趣的話，還是推薦可以入手一本來看看，這邊就附上 &lt;a href=&#34;https://www.books.com.tw/products/0010926506&#34;&gt;博客來&lt;/a&gt; 和 &lt;a href=&#34;https://www.kobo.com/tw/zh/ebook/1iky1ahewzexetqpe6mraw&#34;&gt;Kobo&lt;/a&gt; 連結。有興趣的朋友可以考慮入手看看呦！&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>C#: 時區轉換、民國西元、國曆農曆、中文月份週期</title>
      <link>https://igouist.github.io/post/2020/08/csharp-timezone/</link>
      <pubDate>Sun, 30 Aug 2020 11:58:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/08/csharp-timezone/</guid>
      <description>&lt;p&gt;聊到將時間從 UTC 轉到台灣時間，居然還是聽到朋友表示使用 +8 小時的做法，驚為天人。這種做法可能會造成後續的問題，例如時區並不會跟著變動，或是遇到日光節約等特殊狀況就容易出事。和西元民國轉換直接 -1911 一樣不穩定。&lt;/p&gt;
&lt;p&gt;這篇就用來記錄一下之前看過比較優雅的時區轉換方式，順便將先前存著的時間處理相關資料整理一下，方便之後需要時可以馬上回來查詢。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#timezoneinfo-時區資訊&#34;&gt;TimeZoneInfo: 時區資訊&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#taiwancalendar-西元年轉民國年---taiwanlunisolarcalendar-國曆轉農曆&#34;&gt;TaiwanCalendar: 西元年轉民國年 &amp;amp; &lt;br/&gt; TaiwanLunisolarCalendar: 國曆轉農曆&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#datetimeformatinfo-月週時間的格式&#34;&gt;DateTimeFormatInfo: 月、週、時間的格式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;timezoneinfo-時區資訊&#34;&gt;TimeZoneInfo: 時區資訊&lt;/h2&gt;
&lt;p&gt;轉換方式主要參考自 &lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/10/17/141620&#34;&gt;[食譜好菜] DateTime 具有文化特性的格式化及時區的轉換&lt;/a&gt; 及 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/datetime/converting-between-time-zones&#34;&gt;在各時區間轉換時間&lt;/a&gt;，感謝前人的指引。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於文化特性，也可以參考本站的 &lt;a href=&#34;https://igouist.github.io/post/2021/10/csharp-datatime-tostring-cultureinfo&#34;&gt;菜雞抓蟲: DateTime.ToString() 之我們不一樣 &amp;amp; CultureInfo 文化特性小筆記&lt;/a&gt; 呦。&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 假設現在是要從標準時區 +00:00 轉換到台灣時區，故這邊使用 UtcNow 先取標準世界協調時間&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; nowDateTime = DateTime.UtcNow;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nowDateTime.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yyyy/MM/dd H:mm:ss zzz&amp;#34;&lt;/span&gt;).Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 2020/08/30 15:56:05 +00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ==================================================&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 傳統的 直接對時間做計算的方式…&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; addedDateTime = nowDateTime.AddHours(&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;addedDateTime.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yyyy/MM/dd H:mm:ss zzz&amp;#34;&lt;/span&gt;).Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 2020/08/30 23:56:05 +00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 可以看到儘管時間變動了，時區仍然還在 +00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ==================================================&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 使用 TimeZoneInfo 先取得台北時區&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; timeZone = TimeZoneInfo.FindSystemTimeZoneById(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Taipei Standard Time&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 再使用 TimeZoneInfo 來變更時間&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; convertedDateTime = TimeZoneInfo.ConvertTime(nowDateTime, timeZone);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;convertedDateTime.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yyyy/MM/dd H:mm:ss zzz&amp;#34;&lt;/span&gt;).Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 2020/08/30 23:56:05 +08:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 可以看到除了時間變更以外，時區也切換到 +08:00 了！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面取得台北時區的步驟，可以參照 &lt;a href=&#34;https://docs.microsoft.com/en-us/previous-versions/windows/embedded/gg154758(v=winembedded.80)&#34;&gt;Time Zone IDs&lt;/a&gt; 來查詢想要的時區。這樣的時區切換方式，不僅副作用少，不會因為時區沒轉雷到後續接手的人，也省卻了擔心日光節約等等問題，這種事就交給微軟去煩惱吧！&lt;/p&gt;
&lt;p&gt;另外這邊也逐步放一些時區處理相關的參考資料：&lt;/p&gt;
&lt;h2 id=&#34;taiwancalendar-西元年轉民國年---taiwanlunisolarcalendar-國曆轉農曆&#34;&gt;TaiwanCalendar: 西元年轉民國年 &amp;amp; &lt;br/&gt; TaiwanLunisolarCalendar: 國曆轉農曆&lt;/h2&gt;
&lt;p&gt;可以參考這篇 &lt;a href=&#34;https://kevintsengtw.blogspot.com/2014/06/c.html&#34;&gt;基本題 - C# 西元年轉換取得民國年格式字串 - mrkt 的程式學習筆記&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;內文示範了使用 &lt;code&gt;System.Globalization.TaiwanCalendar&lt;/code&gt; 和 &lt;code&gt;System.Globalization.TaiwanLunisolarCalendar&lt;/code&gt; 來進行安全轉換的作法。&lt;/p&gt;
&lt;p&gt;基本上來說就是指定文化特性中的時間格式（曆法）為農曆，至少依靠微軟爸爸，比自己加減 1911 來得安全多了囧。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; time = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DateTime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2021&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 直接從 TaiwanCalendar 取民國年，自組字串時常用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; taiwanCalendar = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TaiwanCalendar();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;taiwanCalendar.GetYear(time).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 110&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 將文化特性的曆法改成民國年&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; info = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CultureInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zh-TW&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;info.DateTimeFormat.Calendar = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; TaiwanCalendar();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 西元年轉民國年(字串)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;time.ToString(info).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 110/12/1 00:00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 民國年(字串)轉西元年&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; timeString = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;110/12/1&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DateTime.Parse(timeString, info).Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 2021/12/01 00:00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;datetimeformatinfo-月週時間的格式&#34;&gt;DateTimeFormatInfo: 月、週、時間的格式&lt;/h2&gt;
&lt;p&gt;不用再傻傻地手刻陣列「星期一」、「星期二」、「星期三」…之類的了，只要用 &lt;code&gt;DateTimeFormatInfo.CurrentInfo&lt;/code&gt; 的就好啦：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; MonthCh = DateTimeFormatInfo.CurrentInfo.MonthNames;   &lt;span style=&#34;color:#75715e&#34;&gt;// 中文月份名稱列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; MonthEn = DateTimeFormatInfo.InvariantInfo.MonthNames; &lt;span style=&#34;color:#75715e&#34;&gt;// 英文月份名稱列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/se7XLZy.webp&#34;
  alt=&#34;&#34;width=&#34;108&#34; height=&#34;602&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了月份以外，&lt;code&gt;DateTimeFormatInfo.CurrentInfo&lt;/code&gt; 也包含了當地的其他欄位與格式。&lt;/p&gt;
&lt;p&gt;例如 &lt;code&gt;AbbreviatedDayNames&lt;/code&gt; 能拿到 週一、週二、週三…；&lt;code&gt;DayNames&lt;/code&gt; 則會拿到 星期一、星期二、星期三…&lt;/p&gt;
&lt;p&gt;時間格式的話，&lt;code&gt;FullDateTimePattern&lt;/code&gt; 就會拿到 &lt;code&gt;yyyy&#39;年&#39;M&#39;月&#39;d&#39;日&#39; tt hh:mm:ss&lt;/code&gt; 等等&lt;/p&gt;
&lt;p&gt;內容還有許多欄位，有興趣的可以用 LinqPad 來 Dump 看看，或是直接翻閱 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.globalization.datetimeformatinfo?view=netcore-3.1&#34;&gt;DateTimeFormatInfo&lt;/a&gt; 囉！&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2014/06/c.html&#34;&gt;基本題 - C# 西元年轉換取得民國年格式字串 - mrkt 的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/10/17/141620&#34;&gt;[食譜好菜] DateTime 具有文化特性的格式化及時區的轉換&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2019/04/15/095324&#34;&gt;[桌邊服務] DateTime 本身有沒有包含時區的資訊？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/ricochen/2016/02/12/114642&#34;&gt;[C#]UTC時區轉換 - RiCo技術農場&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2010/04/30/Concept-DateTime-TimeZone&#34;&gt;釐清觀念：.NET 日期結構(DateTime) 與 時區轉換&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/compare-datetime-with-diff-timezone&#34;&gt;笨問題 - UTC 與本地時區 DateTime 比較&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/custom-date-and-time-format-strings?redirectedfrom=MSDN&#34;&gt;自訂日期與時間格式字串 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/datetime/converting-between-time-zones&#34;&gt;在各時區間轉換時間 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://siyuantw.blogspot.com/2015/07/c.html&#34;&gt;Siyuan的程式開發筆記: C#民國年西元年轉換 (siyuantw.blogspot.com)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Powershell 美化作戰 —— 字型、執行原則和 oh-my-posh</title>
      <link>https://igouist.github.io/post/2020/08/powershell-beauty/</link>
      <pubDate>Sat, 15 Aug 2020 18:00:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/08/powershell-beauty/</guid>
      <description>&lt;p&gt;最近在兩天內經歷了記憶體死去、系統毀損、機殼碎裂等等，終於電腦重灌。一堆設定都要重弄，正好也是個機會，這篇順手記一下常用好幫手 Powershell 的美化步驟。&lt;/p&gt;
&lt;p&gt;先放一張施工後的圖鎮樓：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/417skJs.webp&#34;
  alt=&#34;&#34;width=&#34;704&#34; height=&#34;177&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以在開始圖示上用右鍵打開選單，之後點選 Powershell；或是 Win + X 打開選單，然後按 I 或 A （後者會以系統管理員身分開啟）就能開啟了。&lt;/p&gt;
&lt;p&gt;如果選單打開還是 CMD 而不是 Powershell 的，可以先去切換成 Powershell，真的是比較好用啦（&lt;a href=&#34;https://www.microsoft.com/zh-tw/p/windows-terminal/9n0dx20hk701?activetab=pivot:overviewtab&#34;&gt;Windows Terminal&lt;/a&gt; 笑而不語）&lt;/p&gt;
&lt;p&gt;剛打開的畫面是這樣的：



&lt;img
  src=&#34;https://image.igouist.net/JE6BTSU.webp&#34;
  alt=&#34;&#34;width=&#34;859&#34; height=&#34;734&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;抱歉，光細明體我就不太行了。所以接下來就從字型這些內建設定開始！&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#基本設定&#34;&gt;基本設定&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝-posh-git&#34;&gt;安裝 posh-git&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#執行原則&#34;&gt;執行原則&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝-oh-my-posh&#34;&gt;安裝 oh-my-posh&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#設定主題&#34;&gt;設定主題&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#撰寫腳本&#34;&gt;撰寫腳本&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;基本設定&#34;&gt;基本設定&lt;/h2&gt;
&lt;p&gt;在上方的標題列按下右鍵，選擇內容&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MkWyIv7.webp&#34;
  alt=&#34;&#34;width=&#34;302&#34; height=&#34;238&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;首先就讓我們修改&lt;strong&gt;字型&lt;/strong&gt;，我這邊還是採用習慣的 &lt;a href=&#34;https://github.com/be5invis/Sarasa-Gothic&#34;&gt;更紗黑體&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另外還有一些推薦的字體，可以參閱前陣子整理的 &lt;a href=&#34;https://igouist.github.io/post/2020/03/visualstudio&#34;&gt;Visual studio 環境設定 —— 字型、套件、快捷鍵&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：還有擴充了 &lt;a href=&#34;https://www.nerdfonts.com/cheat-sheet&#34;&gt;Nerd Fonts&lt;/a&gt; 的等距更紗黑體：&lt;a href=&#34;https://github.com/jonz94/Sarasa-Gothic-Nerd-Fonts&#34;&gt;Sarasa-Gothic-Nerd-Fonts&lt;/a&gt; 可以選擇，Nerd Fonts 能提供許多精美的 icon 讓我們後續改主題的時候大大加分！&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/93httvP.webp&#34;
  alt=&#34;&#34;width=&#34;452&#34; height=&#34;555&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了字型以外，我個人還推薦可以修改一個設定，保證質感 UPUP，那就是&lt;strong&gt;透明度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OGR98I8.webp&#34;
  alt=&#34;&#34;width=&#34;450&#34; height=&#34;558&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我個人習慣採用 80~85% 左右的透明度，搭配黑色背景。在桌面使用的時候看起來就像這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XBFVEmU.webp&#34;
  alt=&#34;&#34;width=&#34;1019&#34; height=&#34;629&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以拉回去和一開始的預設狀況做比較，透明感＝質感。&lt;/p&gt;
&lt;p&gt;另外，背景透明還有意想不到的好處，那就是——&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4Yh5SvJ.webp&#34;
  alt=&#34;&#34;width=&#34;1016&#34; height=&#34;641&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;打指令偷看可以不用切換視窗！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;基於以上兩點，推薦可以調整透明度，找個適合自己的區間。但要注意，如果太透明的話會讓字跟背景糊在一起，很容易眼花，自己斟酌一下。&lt;/p&gt;
&lt;p&gt;以上就做完了基本設定，沒意外要做兩次（一般身分和系統管理員）&lt;/p&gt;
&lt;h2 id=&#34;安裝-posh-git&#34;&gt;安裝 posh-git&lt;/h2&gt;
&lt;p&gt;然而我們並不打算在此停止！因緣際會之下，我拜讀了這篇 &lt;a href=&#34;https://blog.poychang.net/setting-powershell-theme-with-oh-my-posh/&#34;&gt;使用 oh-my-posh 美化 PowerShell 樣式&lt;/a&gt;，頓時驚為天人，跟預設的（請自己再拉回去比對第一張圖）實在是相當有差距，於是當時就直接安裝下來了。&lt;/p&gt;
&lt;p&gt;畢竟這是第二次安裝了，接下來的區段就記錄一下安裝 &lt;a href=&#34;https://github.com/JanDeDobbeleer/oh-my-posh&#34;&gt;oh-my-posh&lt;/a&gt; 主題的流程。&lt;strong&gt;oh-my-posh&lt;/strong&gt; 是受到 Linux 上 &lt;a href=&#34;https://github.com/ohmyzsh/ohmyzsh&#34;&gt;oh-my-zsh&lt;/a&gt; 的&lt;strong&gt;啟發&lt;/strong&gt;誕生的，總之就是個挺讚讚的主題載入工具。&lt;/p&gt;
&lt;p&gt;而且安裝相當方便，只需要從 Microsoft Store 或直接打 &lt;code&gt;Install-Module&lt;/code&gt; 指令，就能從 &lt;a href=&#34;https://www.powershellgallery.com/&#34;&gt;PowerShell Gallery&lt;/a&gt; 把模組安裝下來囉！&lt;/p&gt;
&lt;p&gt;在安裝 oh-my-posh 之前，由於顯示的內容包含 Git ，因此我們還要先下載另一個套件 &lt;code&gt;posh-git&lt;/code&gt;。讓我們用&lt;strong&gt;系統管理員&lt;/strong&gt;身份開啟 Powershell，並輸入以下指令來進行安裝：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Install-Module posh-git
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;到這一步，會有幾種狀況：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提示安裝 NuGet：安裝就行了&lt;/li&gt;
&lt;li&gt;提示不安全儲存庫：安啦，選是就對了&lt;/li&gt;
&lt;li&gt;跳出錯誤，顯示「因為這個系統上已停用指令碼執行」云云，則接著看下一段落&lt;/li&gt;
&lt;li&gt;什麼事都沒發生，很順利地裝好了，請跳過下一段落&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;執行原則&#34;&gt;執行原則&lt;/h2&gt;
&lt;p&gt;如果跳出「因為這個系統上已停用指令碼執行…」的情況，代表遇到&lt;strong&gt;執行原則&lt;/strong&gt;的部份。因為安全性考量，預設是不能執行 Powershell 腳本的，連帶也不能使用 Install-Module 這類方法。&lt;/p&gt;
&lt;p&gt;關於執行原則，可以參閱這篇 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10028377&#34;&gt;Windows PowerShell 基本操作 - 執行 Windows PowerShell 腳本&lt;/a&gt; 的說明。該系列也挺實用的，值得一看，這邊我們就節錄一段來說明執行原則的種類：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Restricted&lt;/strong&gt; ：&lt;strong&gt;關閉腳本檔的執行功能&lt;/strong&gt;，這是&lt;strong&gt;預設&lt;/strong&gt;的設定值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AllSigned&lt;/strong&gt; ：只允許執行&lt;strong&gt;受信任發行者&lt;/strong&gt;簽署過的腳本檔。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RemoteSigned&lt;/strong&gt; ：在&lt;strong&gt;本機電腦所撰寫的腳本檔，不需要簽署就可執行&lt;/strong&gt;；但是從網際網路（例如：email、MSN Messenger）下載的腳本檔就必須經過受信任發行者的簽署才能執行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unrestricted&lt;/strong&gt; ：&lt;strong&gt;任何腳本檔皆可被執行&lt;/strong&gt;，但是於執行網際網路下載的腳本檔時，會先出現警告的提示視窗。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果要確認目前的執行原則，可以使用 &lt;code&gt;Get-ExecutionPolicy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;這個分類也是為了保護使用者，不要亂執行一些來路不行了奇怪腳本，整個電腦打包起來送人。不過我們身為 &lt;del&gt;白目&lt;/del&gt; 工程師，難免會有要裝套件和自己寫腳本的時候，這邊就直接調整為 Unrestricted 全面開啟吧！&lt;/p&gt;
&lt;p&gt;調整執行原則的語法如下（必須在系統管理員身分時才有效果呦）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Set-ExecutionPolicy Unrestricted
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;調整完就可以繼續下載囉！&lt;/p&gt;
&lt;p&gt;像我重灌的狀況，提示會如下圖，如果已經安裝過 Nuget 提供者，提示應該會更少。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/UoGkW7o.webp&#34;
  alt=&#34;&#34;width=&#34;1008&#34; height=&#34;577&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;安裝-oh-my-posh&#34;&gt;安裝 oh-my-posh&lt;/h2&gt;
&lt;p&gt;接著讓我們安裝 oh-my-posh：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Install-Module oh-my-posh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/LQuE2hD.webp&#34;
  alt=&#34;&#34;width=&#34;977&#34; height=&#34;223&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝完畢後就可以來試試看囉！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更新：Oh-my-posh 也可以從 Microsoft 搜尋並安裝囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3WoVsvL.webp&#34;
  alt=&#34;Image&#34;width=&#34;1079&#34; height=&#34;323&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;設定主題&#34;&gt;設定主題&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;更新：由於 Oh-my-posh 設定主題的語法已經改變，因此移除 2020 時的內容，改為 2023 重灌電腦時找到的指令。&lt;/p&gt;
&lt;p&gt;因為 Oh-my-posh 還蠻頻繁更新的，建議後續的操作可以開著 &lt;a href=&#34;https://ohmyposh.dev/docs/installation/customize&#34;&gt;Oh My Posh&lt;/a&gt; 的官方文檔來排查一下&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;要更改主題的話，我們會需要使用 &lt;code&gt;oh-my-posh init pwsh&lt;/code&gt; 這個指令，並且將該主題的 Json 檔案傳遞給 &lt;code&gt;--config&lt;/code&gt;&lt;/strong&gt;，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;oh-my-posh init pwsh --config &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$env&lt;span style=&#34;color:#e6db74&#34;&gt;:POSH_THEMES_PATH/powerlevel10k_rainbow.omp.json&amp;#34;&lt;/span&gt; | Invoke-Expression
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我們丟了一個 powerlevel10k_rainbow 這個主題的 Json 檔案路徑給 oh-my-posh，然後呼叫 &lt;code&gt;Invoke-Expression&lt;/code&gt; 執行它，這時候主題就會變啦～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：這邊用到一組環境變數 &lt;code&gt;$env:POSH_THEMES_PATH&lt;/code&gt; 實際上是一個資料夾路徑，Oh-my-posh 安裝的主題都會放在這個資料夾裡：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lWBHJc3.webp&#34;
  alt=&#34;Image&#34;width=&#34;509&#34; height=&#34;65&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果你有自己下載的主題，可以也丟到 &lt;code&gt;POSH_THEMES_PATH&lt;/code&gt; 的位置，或是直接更改 &lt;code&gt;--config&lt;/code&gt; 的路徑就可以了。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;每個主題的樣式都不大一樣，可以翻一下 Oh-my-posh 的 &lt;a href=&#34;https://ohmyposh.dev/docs/themes&#34;&gt;Themes&lt;/a&gt; 頁面挑個喜歡的。&lt;/p&gt;
&lt;h2 id=&#34;撰寫腳本&#34;&gt;撰寫腳本&lt;/h2&gt;
&lt;p&gt;聰明的你一定發現了，每次重開都要重輸入一次 &lt;code&gt;init&lt;/code&gt; 實在是很麻煩。因此我們接著要設定讓它能一打開就載入主題。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;當 Powershell 開啟時，預設會去讀取使用者的設定檔（Profile）&lt;/strong&gt;，我們就是要把這些指定加到設定檔裡，讓 Powershell 一打開就能美美的。&lt;/p&gt;
&lt;p&gt;在 Powershell 裡輸入 &lt;code&gt;$PROFILE&lt;/code&gt; 就能取得當前的設定檔位置，通常來說會在 &lt;code&gt;我的文件\WindowsPowerShell&lt;/code&gt; 底下。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/kRKV112.webp&#34;
  alt=&#34;&#34;width=&#34;896&#34; height=&#34;75&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：Powershell 啟動時會依序檢查四個位置來載入設定檔，分別為&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有使用者及所有 Shell: &lt;code&gt;$PSHOME\profile.ps1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;所有使用者的 Powershell: &lt;code&gt;$PSHOME\Microsoft.PowerShell_profile.ps1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;目前登入者的所有 Shell: &lt;code&gt;$Home\My Documents\WindowsPowerShell\profile.ps1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;目前使用者的 Powershell: &lt;code&gt;$Home\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中的變數&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$PSHOME&lt;/code&gt; 是指 Powershell 的安裝目錄，通常在 system32 的 WindowsPowerShell 底下&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$Home&lt;/code&gt; 則是使用者的主目錄，也就是大家熟悉的 users/{你的名稱}&lt;/li&gt;
&lt;li&gt;而前面提到的 &lt;code&gt;$PROFILE&lt;/code&gt; 就是指「目前使用者的 Powershell」這組&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;對設定檔有興趣的朋友可以參照保哥的文章及官方文檔：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2013/01/02/How-to-auto-load-ps1-script-using-Windows-PowerShell-Profiles&#34;&gt;如何在 Powershell 開啟時自動引入常用的 ps1 指令檔腳本 | The Will Will Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.2&#34;&gt;關於設定檔 - PowerShell | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;接著我們就前往此處去修改 Powershell 的腳本，如果發現並沒有這個檔案，那還請自己建立一個，檔名和路徑請和 &lt;code&gt;$PROFILE&lt;/code&gt; 一致，像我就取 &lt;code&gt;Microsoft.PowerShell_profile.ps1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/eJ8A5iY.webp&#34;
  alt=&#34;&#34;width=&#34;443&#34; height=&#34;137&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;之後開啟檔案，貼上我們引入套件和設定主題的指令：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ps1&#34; data-lang=&#34;ps1&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;oh-my-posh init pwsh --config &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$env:POSH_THEMES_PATH&lt;span style=&#34;color:#e6db74&#34;&gt;/powerlevel10k_rainbow.omp.json&amp;#34;&lt;/span&gt; | Invoke-Expression
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;再次補充：oh-my-posh 改版之後一些語法有改變，如果有在 &lt;code&gt;Set-Theme&lt;/code&gt; 這步的時候遇到「無法辨識…」問題的朋友，可以嘗試改成用 &lt;code&gt;Set-PoshPrompt -Theme Paradox&lt;/code&gt; 試試看。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著讓我們重開 Powershell&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/1kYjQSO.webp&#34;
  alt=&#34;Image&#34;width=&#34;1005&#34; height=&#34;152&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見它已經順利載入主題囉！&lt;/p&gt;
&lt;p&gt;因為我覺得切主題前跑那些 Powershell 提示之類的有點醜，所以我會回去 Profile 補一行 Clear：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ps1&#34; data-lang=&#34;ps1&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;oh-my-posh init pwsh --config &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$env:POSH_THEMES_PATH&lt;span style=&#34;color:#e6db74&#34;&gt;/powerlevel10k_rainbow.omp.json&amp;#34;&lt;/span&gt; | Invoke-Expression
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;clear
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;也可以在這裡寫一些 function 來使用，像我個人之前做 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium&#34;&gt;我要訂便當&lt;/a&gt; 因為懶得每次都 cd 到腳本所在的位置還要 Pyhton 執行，所以就直接包成 &lt;code&gt;Get-Bandon&lt;/code&gt; 的方法直接叫，真的舒服，推薦嘗試&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;如果有想要自己試試新增主題，或是想像我一樣做小修改的，可以到 &lt;code&gt;$env:POSH_THEMES_PATH&lt;/code&gt; 這個路徑的資料夾，找到 &lt;code&gt;.omp.json&lt;/code&gt; 檔案來動手&lt;/p&gt;
&lt;p&gt;例如說把 &lt;code&gt;powerlevel10k_rainbow.omp.json&lt;/code&gt; 開啟後，就可以看見組出那一串資訊的過程，也就可以隨意更改囉。我就特愛把一些 icon 都改成 &lt;code&gt;°д°&lt;/code&gt;，感覺活潑多了（？）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：該路徑會根據安裝 &lt;code&gt;oh-my-posh&lt;/code&gt; 的位置而變動，基本上開 Powershell 直接從環境變數的路徑下去找比較快。真的找不到的話也可以下載別人的主題改一改，再修改 `&amp;ndash;config`` 的路徑指到自己改好的 json 就好囉&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;順便提一下，載入使用者設定對用到 Powershell 的地方都有效喲！&lt;/p&gt;
&lt;p&gt;例如 Visual Studio Code 的 Powershell&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fqtRmwR.webp&#34;
  alt=&#34;&#34;width=&#34;554&#34; height=&#34;206&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;或是 Visual Studio 的開發人員用 Powershell&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ulEehB0.webp&#34;
  alt=&#34;&#34;width=&#34;593&#34; height=&#34;196&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;都是會吃到個人使用者的設定的。&lt;/p&gt;
&lt;p&gt;那麼今天就到這裡，最後再把完成品跟預設狀況比對一次吧～&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JE6BTSU.webp&#34;
  alt=&#34;&#34;width=&#34;859&#34; height=&#34;734&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/99DaSVP.webp&#34;
  alt=&#34;&#34;width=&#34;1020&#34; height=&#34;472&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;2023 回來更新文章，順便附個現況：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JOVI7hg.webp&#34;
  alt=&#34;Image&#34;width=&#34;1060&#34; height=&#34;480&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;同場加映：&lt;/p&gt;
&lt;p&gt;跟朋友志得意滿地說明了 Powershell 美化作戰後，對方只說了淡淡的一句&lt;/p&gt;
&lt;p&gt;「哦，我都直接 &lt;a href=&#34;https://cmder.app/&#34;&gt;Cmder&lt;/a&gt;」&lt;/p&gt;
&lt;p&gt;那又是另一個故事了……&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.poychang.net/setting-powershell-theme-with-oh-my-posh/&#34;&gt;使用 oh-my-posh 美化 PowerShell 樣式 - POY CHANG&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ppundsh.github.io/posts/ad6e/&#34;&gt;PowerShell 美化：oh my posh - Flymia 凡事用心之事&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pcion123.github.io/2020/03/08/powershell-improve/&#34;&gt;美化PowerShell - oh-my-posh - Pcion&amp;rsquo;s note&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10028377&#34;&gt;Windows PowerShell 基本操作 - 執行 Windows PowerShell 腳本&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://officeguide.cc/powershell-set-execution-policy-remote-signed/&#34;&gt;PowerShell 更改執行原則，解決無法執行 ps1 指令稿問題&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 字串插值 (String interpolation) 的格式化</title>
      <link>https://igouist.github.io/post/2020/08/csharp-string-interpolation/</link>
      <pubDate>Sun, 09 Aug 2020 20:35:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/08/csharp-string-interpolation/</guid>
      <description>&lt;p&gt;自從 C# 有了 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/tokens/interpolated&#34;&gt;字串插值&lt;/a&gt; 這東西之後，我就一直是愛用者。畢竟比起 &lt;code&gt;string.format&lt;/code&gt; 這東西可是看起來優雅多了。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;哈囉，{userName} 您的點數將於 {cutoffTime} 到期。&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;簡潔又明瞭，一眼就能理解字串內容。實在是挺方便，後來發現這東西還有一些延伸用法，這邊就稍加紀錄一下：&lt;/p&gt;
&lt;p&gt;字串插值中能夠做&lt;strong&gt;簡易計算&lt;/strong&gt;，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;您輸入的數值為：{a}、{b}。他們相加為：{a + b}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同時，在字串插值時可以針對內容作格式化，只需要用 &lt;code&gt;:&lt;/code&gt; 來區隔，妥善運用可以省下一堆 &lt;code&gt;ToString()&lt;/code&gt; 的空間。&lt;/p&gt;
&lt;p&gt;例如當我們要將&lt;strong&gt;時間&lt;/strong&gt;格式化的時候，就可以：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; date = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; DateTime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2020&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;您的商品已於 {date:yyyy/MM/dd} 抵達。&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 您的商品已於 2020/08/09 抵達。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;另外，&lt;strong&gt;數值&lt;/strong&gt;當然也可以格式化，不過數值的應用比較複雜，主要是用來定下小數點、百分比等符號的位置。
可以參見 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/custom-numeric-format-strings&#34;&gt;自訂數值格式字串 - Microsoft Docs&lt;/a&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; cost = &lt;span style=&#34;color:#ae81ff&#34;&gt;2100&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message1 = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;您的商品一共是 {cost:#,###} 元&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 用 # 可以替數字預留位置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 您的商品一共是 2,100 元&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message2 = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;您的商品一共是 {cost:#,###.00} 元&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 也可以用 0 來預留位置，若該數字有值就會顯示該數字，沒有就會自動補 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 您的商品一共是 2,100.00 元&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而格式化也支援&lt;strong&gt;列舉&lt;/strong&gt;，在同時顯示列舉的意義和值的時候會很有幫助：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// public enum Color { Red = 1, Blue = 2, Green = 3 };&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; color = Color.Blue;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;編號 {color:D} 是 {color:G}&amp;#34;&lt;/span&gt;.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 編號 2 是 Blue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;承上部分，由於 &lt;code&gt;:&lt;/code&gt; 在字串插值裡已經有特殊意義了，因此想在字串插值中使用&lt;strong&gt;三元運算子&lt;/strong&gt;，必須先用 &lt;code&gt;( )&lt;/code&gt; 括起來，如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; message = &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;您的包裹 {(isArrival ? &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;已&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; : &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;尚未&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;)} 抵達門市&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;對字串插值有興趣的朋友，也可用&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/tutorials/exploration/interpolated-strings&#34;&gt;微軟官方的教學課程&lt;/a&gt;來試試呦。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/eyelash/2019/07/06/205834&#34;&gt;【C# 6.0】字串插補（更容易插入變數） - EY＊研究院&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/tokens/interpolated&#34;&gt;字串插值 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/tutorials/string-interpolation&#34;&gt;C# 中的字串插補 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/custom-numeric-format-strings&#34;&gt;自訂數值格式字串 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/enumeration-format-strings&#34;&gt;列舉格式字串 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/formatting-types&#34;&gt;.NET 中的格式類型 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 使用 System.Environment 取得環境資訊、特殊資料夾路徑</title>
      <link>https://igouist.github.io/post/2020/08/system-environment/</link>
      <pubDate>Sun, 02 Aug 2020 13:31:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/08/system-environment/</guid>
      <description>&lt;p&gt;有時候我們會需要取得一些系統資訊，例如說取得設備和當前使用者等資料來寫 Log，或是取得特殊資料夾路徑、讀取環境變數等等。這些時候就可以使用 &lt;code&gt;System.Environment&lt;/code&gt; ，這邊就稍微紀錄一下用法。&lt;/p&gt;
&lt;p&gt;先列出幾個常用的&lt;strong&gt;環境資訊&lt;/strong&gt;，詳細可查詢的內容可以到 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.environment?view=netcore-3.1&#34;&gt;Environment Class&lt;/a&gt; 查詢：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8L67T46.webp&#34;
  alt=&#34;&#34;width=&#34;1420&#34; height=&#34;460&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;資料夾路徑&lt;/strong&gt;則需要用 &lt;code&gt;Environment.GetFolderPath&lt;/code&gt; 搭配 &lt;code&gt;Environment.SpecialFolder&lt;/code&gt; 列舉使用，該列舉包含資料夾可以到 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.environment.specialfolder?view=netcore-3.1&#34;&gt;Environment.SpecialFolder Enum&lt;/a&gt; 查詢。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aOQ30KB.webp&#34;
  alt=&#34;&#34;width=&#34;1504&#34; height=&#34;352&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除此之外還有一些實用的方法，例如用 &lt;code&gt;GetEnvironmentVariable&lt;/code&gt; 取得&lt;strong&gt;環境變數&lt;/strong&gt;，如此就可以將部分資訊交由環境變數來決定：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pRj4yEA.webp&#34;
  alt=&#34;&#34;width=&#34;1258&#34; height=&#34;316&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而最常用的應該就是 NewLine 了，畢竟不同環境的&lt;strong&gt;換行符號&lt;/strong&gt;可能會不同，養成用 NewLine 的習慣總是好的：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/jvjNnJK.webp&#34;
  alt=&#34;&#34;width=&#34;1392&#34; height=&#34;388&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jjnnykimo.pixnet.net/blog/post/21585496&#34;&gt;C# 取得系統特殊目錄及環境變數 - 狼翔天地&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://apmtechtony.blogspot.com/2017/09/c-environmentspecialfolder.html&#34;&gt;C# 取得系統特殊目錄及環境變數 Environment.SpecialFolder 與 Environment.GetEnvironmentVariable 差別&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 程式碼風格備忘</title>
      <link>https://igouist.github.io/post/2020/07/code-style/</link>
      <pubDate>Sat, 25 Jul 2020 00:16:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/code-style/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;前言：本篇是整理公司規範和網路文章後，方便我自己在各個場所也能回來查閱使用的，故仍會不定時修改（畢竟我這人挺三心二意的）。另外本篇有重新調整過行距，發現有點跑版的朋友可以先 Ctrl+F5 一下，感謝閱讀。&lt;/p&gt;
&lt;p&gt;如果你想知道的是如何寫出更優雅、更乾淨、品質更高的程式碼，那並不是該看這篇我個人的備忘錄，我會建議可以閱讀&lt;a href=&#34;https://www.tenlong.com.tw/products/9789862017050?list_name=e-106&#34;&gt;《無暇的程式碼》&lt;/a&gt;。或是可以參考這幾篇，我覺得都寫得很好：&lt;a href=&#34;https://ithelp.ithome.com.tw/users/20107637/ironman/1927&#34;&gt;可不可以不要寫糙 code&lt;/a&gt;、&lt;a href=&#34;https://ithelp.ithome.com.tw/users/20107637/ironman/1927&#34;&gt;Clean Code 無瑕的程式碼閱讀筆記&lt;/a&gt;、&lt;a href=&#34;https://ithelp.ithome.com.tw/users/20112160/ironman/1988&#34;&gt;易讀程式之美學&lt;/a&gt;，共勉之。&lt;/p&gt;
&lt;p&gt;如果你是正巧路過並且也寫 C#，希望這篇能讓你做為參考。但請記得，程式碼風格沒有絕對，最終還是回歸到團隊能否接受和將來的可維護性去考慮，畢竟教條是死的，人是活的。了解這樣做背後的原因，以及為自己寫的程式碼負責，這些都比對著隻字片語斤斤計較更加重要。&lt;/p&gt;
&lt;p&gt;Nothing is true, everything is permitted. —— Assassin&amp;rsquo;s creed.&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2025.01.03 補充：&lt;br/&gt;
剛好看到保哥分享了他們家的 &lt;a href=&#34;https://gist.github.com/doggy8088/74ae840e7f5db554d7417007f0b26671&#34;&gt;C# 程式碼撰寫規範&lt;/a&gt;，有需要的朋友也可以作為參考&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;命名原則&#34;&gt;命名原則&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;想像下一個接手你程式碼的人是個暴力傾向的重度精神病患者&lt;/p&gt;
&lt;p&gt;而且他知道你住在哪。　　—— 《無瑕的程式碼》&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;strong&gt;有意義的命名&lt;/strong&gt;，請重視描述性。除了迴圈計數器例外&lt;/li&gt;
&lt;li&gt;盡量不要超過五個單字&lt;/li&gt;
&lt;li&gt;業界和慣例中有對應縮寫時可以使用縮寫&lt;/li&gt;
&lt;li&gt;承上，縮寫兩個字母時全大寫，三個字母以上時只第一字大寫&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;命名空間&#34;&gt;命名空間&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;基礎類別庫：{組織} . {大類/應用範圍} . {小類/專案名稱}
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MyStudio.Libs.Basic&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MyStudio.Libs.Web.BaseTools&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;專案類別庫：{專案名稱} . {子專案/類別/用途}
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MyProject.Permiss.Proxy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MyProject.Permiss.Repository&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;類別&#34;&gt;類別&lt;/h3&gt;
&lt;p&gt;使用&lt;strong&gt;名詞&lt;/strong&gt;或名詞片語，不使用任何前綴，須和檔案名稱相同
類別應該要能夠在 25 個字之內做出描述&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基底類別 (Basic Class)&lt;/strong&gt;：後綴 &lt;strong&gt;Base&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public class ProductBase&lt;/code&gt; （=產品基底類別）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一般類別 (General Class)&lt;/strong&gt;  註：抽象類別與一般類別的命名相同
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public class Product&lt;/code&gt; （=產品類別）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集合類別 (Collection Class)&lt;/strong&gt;：後綴 &lt;strong&gt;Collection&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public class ProductCollection&lt;/code&gt; （=產品集合類別）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工廠類別 (Factory Class)&lt;/strong&gt;：後綴 &lt;strong&gt;Factory&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public class ProductFactory&lt;/code&gt; （=產品工廠類別）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Help 類別&lt;/strong&gt;：應以 Help 名稱、Help 性質方式命名
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public class SystemTimeHelp&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有使用 Partial 的類別&lt;/strong&gt;：檔名按照類別階層命名
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public partial class Core&lt;/code&gt; 此類別分散到 Album 和 Banner
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;internal Public class Album&lt;/code&gt;  -&amp;gt; 檔名為 &lt;code&gt;Core.Album.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;internal Public class Banner&lt;/code&gt; -&amp;gt; 檔名為 &lt;code&gt;Core.Banner.cs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;列舉&lt;/strong&gt;：大駱駝峰，並後綴 &lt;strong&gt;Enum&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public enum KeywordTypeEnum&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;介面&lt;/strong&gt;：大駱駝峰，前綴 &lt;code&gt;I&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ICarFactory&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;變數屬性&#34;&gt;變數、屬性&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;公開變數使用大駱駝峰&lt;/strong&gt;，例如 &lt;code&gt;public string Name { set; get; }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;私有變數使用小駱駝峰&lt;/strong&gt;，例如 &lt;code&gt;private int approvalRating&lt;/code&gt;，看專案決定需不需要加上底線當前綴&lt;/p&gt;
&lt;p&gt;常數一律大寫，但建議使用 static readonly 然後使用大駱駝峰。因為前輩的文件表示過使用常數連結 DLL 時如果修改 DLL 的常數再參考但尚未建置時會導致一些神奇的錯誤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const double PI = 3.14159;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static readonly double Pi = 3.14159;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;命名變數時的注意事項：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包含邊界的極值優先使用 &lt;code&gt;min&lt;/code&gt; 與 &lt;code&gt;max&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;閉區間優先使用 &lt;code&gt;first&lt;/code&gt; 與 &lt;code&gt;end&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;半開放區間優先使用 &lt;code&gt;begin&lt;/code&gt; 與 &lt;code&gt;end&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boolean 變數開頭必須使用 &lt;code&gt;is&lt;/code&gt; &lt;code&gt;can&lt;/code&gt; &lt;code&gt;has&lt;/code&gt; &lt;code&gt;should&lt;/code&gt; 或有狀態差異的形容詞&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;宣告變數時的注意事項：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;變數越多越難記得所有變數
&lt;ul&gt;
&lt;li&gt;減少沒用到的、或對可讀性沒有幫助的變數&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;變數存活的範圍越大，就必須記越久
&lt;ul&gt;
&lt;li&gt;盡量&lt;strong&gt;避免使用全域變數&lt;/strong&gt;，縮限並控制所有變數的範圍&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;變數越常改變越難記得目前的數值和意義
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;偏好單次寫入變數&lt;/strong&gt;，盡量不要過度重複寫入造成混亂&lt;/li&gt;
&lt;li&gt;當變數內容改變，就可以考慮宣告成新的變數&lt;/li&gt;
&lt;li&gt;變數的宣告和其命名都是為了讓閱讀者&lt;strong&gt;能直接看出改變的差異或當下變數的狀態&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;方法&#34;&gt;方法&lt;/h3&gt;
&lt;p&gt;一律使用大駱駝峰，前綴字規定如下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;擷取&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Get&lt;/code&gt;：從資料庫抓回資料&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Search&lt;/code&gt;：頁面或定義為搜尋時&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Find&lt;/code&gt;：在既有的資料集合找資料&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fetch&lt;/code&gt;：從遠端 (透過API) 獲取資料，例如：`FetchUsers()&lt;/li&gt;
&lt;li&gt;&lt;code&gt;load&lt;/code&gt;：從本地端加載資料，例如：&lt;code&gt;LoadFile()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修改&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Update&lt;/code&gt;：對資料庫的異動&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Modify&lt;/code&gt;：現有變數或資料集合的變動&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建立&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Insert&lt;/code&gt;：資料庫的資料建立&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Create&lt;/code&gt;：資料集合或物件的建立&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Generate&lt;/code&gt;：單純產生特定資料供使用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;刪除&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Delete&lt;/code&gt;：資料庫的資料刪除（資料將不存在）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Remove&lt;/code&gt;：資料集合的資料移除／資料間的關係移除（資料仍存在）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;其他&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Convert&lt;/code&gt;：代碼轉文字／編碼之間的轉換&lt;/li&gt;
&lt;li&gt;&lt;code&gt;calculate/calc&lt;/code&gt;：通過計算獲取資料，例如：&lt;code&gt;CalcBMI()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;show&lt;/code&gt;：顯示物件，例如：&lt;code&gt;ShowDialog()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;on&lt;/code&gt;：定義 event 的時候使用，像是 &lt;code&gt;OnClick()&lt;/code&gt;, &lt;code&gt;OnChange()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handle&lt;/code&gt;：當 &lt;code&gt;OnClick&lt;/code&gt; 之類的 event 發生時所觸發的 function&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;方法中用到的參數視作私有變數&lt;/strong&gt;，使用小駱駝峰，如 &lt;code&gt;(string name, int age)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果有泛型直接使用 &lt;code&gt;T&lt;/code&gt; 做表示（如 &lt;code&gt;List&amp;lt;T&amp;gt; T1, List&amp;lt;T&amp;gt; T2&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;其他注意事項：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;比起弄很多大櫃子然後把東西都塞進去，分工明確的小抽屜更方便靈活&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;輸入參數應該越少越好，如果參數高於三個以上，試著&lt;strong&gt;換個思路&lt;/strong&gt;重構這個方法&lt;/li&gt;
&lt;li&gt;輸入參數&lt;strong&gt;不要使用傳入 Boolean 來改變類別和函式的行為&lt;/strong&gt;（違反單一職責）&lt;/li&gt;
&lt;li&gt;有多載的需求時，將輸入參數最多的宣告設為 virtual，其他較少輸入參數的則以呼叫最多輸入參數的函式來完成建構&lt;/li&gt;
&lt;li&gt;如果&lt;strong&gt;類別的名稱已經有足夠的說明，則方法名稱就不需要再複誦一次&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;例如 &lt;code&gt;ProductService.GetProduct()&lt;/code&gt; 就是贅詞&lt;/li&gt;
&lt;li&gt;可以改為 &lt;code&gt;ProductService.Get()&lt;/code&gt; 就好&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;呼叫方法時，如果傳入參數無法直接識別內容，則應使用具名傳遞
&lt;ul&gt;
&lt;li&gt;例如 &lt;code&gt;Get(productId, true)&lt;/code&gt; 很難看出來 true 代表的意思&lt;/li&gt;
&lt;li&gt;可以改為 &lt;code&gt;Get(productId, isInStock: true)&lt;/code&gt; 就好&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;回傳值除了單純的運算式以外，一律回傳變數&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;2022.01.25 補充：&lt;/p&gt;
&lt;p&gt;最近在命名方法時，關於動詞的部份我會參考兩個實用的東東，這邊也分享給各位：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/EngTW/English-for-Programmers&#34;&gt;EngTW/English-for-Programmers: 《程式英文》：用英文提昇程式可讀性 (github.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.2#common-verbs&#34;&gt;已核准的 PowerShell 命令動詞 - PowerShell | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;斷行與縮排&#34;&gt;斷行與縮排&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;整潔的程式碼讀起來就像一篇優美的散文&lt;/p&gt;
&lt;p&gt;　　—— 《無瑕的程式碼》&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;不要過多的斷行，也不要都擠在一起，&lt;strong&gt;組織成不同的段落&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;方法之間空一行&lt;/li&gt;
&lt;li&gt;參數過長時斷至下一行&lt;/li&gt;
&lt;li&gt;程式碼單行欄位數目以不超過 100~120 字元為原則&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不要省略括弧，&lt;strong&gt;讓程式碼範疇／區塊明顯可見&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;迴圈和 IF 等區塊確實斷行和使用括弧，以維持可閱讀性&lt;/li&gt;
&lt;li&gt;運算式的優先部分也要加上括弧，方便一眼看出計算順序&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使 code 的&lt;strong&gt;順序流暢&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;參數按照傳入的順序排列&lt;/li&gt;
&lt;li&gt;相似方法之間的傳入參數順序盡可能一致&lt;/li&gt;
&lt;li&gt;相關的宣告和操作放在一起&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;當因為參數過多的時候必須斷到下一行時，從第一個參數之前就開始斷行
&lt;ul&gt;
&lt;li&gt;因本條規則斷行後，則一個參數獨立一行，必要時具名，使閱讀時能直接往下閱讀&lt;/li&gt;
&lt;li&gt;不過如果方法參數過多，應先考慮是否重構該方法，極可能違反單一職責&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;風格一致、風格一致、風格一致&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;邏輯運算&#34;&gt;邏輯運算&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;儘管區塊只有一行（例如 if else），&lt;strong&gt;仍一律使用大括號&lt;/strong&gt;，避免改寫時沒注意到&lt;/li&gt;
&lt;li&gt;條件式的左側為「變化值」、右側則是前者的「比較基準」
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;符合口語邏輯&lt;/strong&gt;：如果你高於 180 公分 =&amp;gt; &lt;code&gt;if(lenght &amp;gt; 180)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;處理 if/else 的順序時，大多有以下思路
&lt;ul&gt;
&lt;li&gt;先處理&lt;strong&gt;肯定&lt;/strong&gt;的再處理否定的（如：if 成功 else 失敗）&lt;/li&gt;
&lt;li&gt;先處理&lt;strong&gt;簡單&lt;/strong&gt;的再處理複雜的（如：if 吃了一顆糖 else 吃了兩顆糖以上）&lt;/li&gt;
&lt;li&gt;先處理比較&lt;strong&gt;有趣或明顯&lt;/strong&gt;的（舉例舉不出來= =）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;三元運算式&#34;&gt;三元運算式&lt;/h3&gt;
&lt;p&gt;簡單卻繁瑣的時候使用，但是複雜的時候就乖乖用 if else。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; gender = (member.Sex == &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;F&amp;#34;&lt;/span&gt;) ? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;女性&amp;#34;&lt;/span&gt; : &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;男性&amp;#34;&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 很棒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; result = (exponent &amp;gt;= &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)? mantissa * (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &amp;lt;&amp;lt; exponent): mantissa / (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &amp;lt;&amp;lt; -exponent)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ？？？&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;迴圈&#34;&gt;迴圈&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;迴圈不要用 do/while
&lt;ul&gt;
&lt;li&gt;因為大多數方法和區塊邏輯判斷都在前面，因此使用後置條件會影響閱讀性&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;減少巢狀結構，不要玩波動拳
&lt;ul&gt;
&lt;li&gt;減少巢狀的有效方法是「&lt;strong&gt;儘早返回&lt;/strong&gt;」：直接離開當前的函式&lt;/li&gt;
&lt;li&gt;迴圈中可以使用 &lt;code&gt;continue&lt;/code&gt; 或 &lt;code&gt;break&lt;/code&gt; 來直接進入下一個或離開&lt;/li&gt;
&lt;li&gt;在方法／函式中可以先處理能直接 &lt;code&gt;return&lt;/code&gt; 結果離開的區塊&lt;/li&gt;
&lt;li&gt;發生錯誤或非預期結果時可以直接擲出錯誤 &lt;code&gt;throw new Exception&lt;/code&gt; 中斷程式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;存取修飾詞&#34;&gt;存取修飾詞&lt;/h3&gt;
&lt;p&gt;型別的存取層級，用來控制其他程式碼能不能使用這些型別或內容成員，也就是&lt;strong&gt;指定能夠存取的範圍，進而避免誤用、亂用或其他錯誤&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Public
&lt;ul&gt;
&lt;li&gt;可以讓同組件和參考該組件的任何其他程式碼存取&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Private
&lt;ul&gt;
&lt;li&gt;只能由相同類別或結構中的程式碼存取&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Protected
&lt;ul&gt;
&lt;li&gt;只能由相同類別或結構，或是從該類別衍生的類別存取&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Internal / Friend
&lt;ul&gt;
&lt;li&gt;可由相同組件的任何程式碼存取，但是其他組件的程式碼不能存取&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Static / Shared
&lt;ul&gt;
&lt;li&gt;用來宣告靜態成員&lt;/li&gt;
&lt;li&gt;此成員屬於型別本身而不屬於任何物件（＝全實體共用同個成員）&lt;/li&gt;
&lt;li&gt;不能用在索引子 (Indexer)、解構函式 (Destructor) 以及不在類別裡的型別&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;註解&#34;&gt;註解&lt;/h3&gt;
&lt;p&gt;基本上對各個格式（如結構、函數）都必須加上說明註解，其中的參數和成員（例如列舉）也必須說明。
VS 中 /// 就會幫忙把參數註解格式打好。例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C#&#34; data-lang=&#34;C#&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// 資料庫連線類別&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DatabaseConstants&lt;/span&gt; : IDatabaseConstants
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;重點在於：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;簡短敘述，避免模稜兩可或不知去向的代名詞（例如：更新那個東東）&lt;/li&gt;
&lt;li&gt;描述函式行為，例如（計算檔案的行數 =&amp;gt; 計算檔案中的換行位元個數(\n)）&lt;/li&gt;
&lt;li&gt;描述處理過程的原因，要知道做了什麼可以直接看程式碼，要知道為什麼做只能看註解&lt;/li&gt;
&lt;li&gt;常數旁邊一定要做原因或用途的說明&lt;/li&gt;
&lt;li&gt;函式可以加上範例 &lt;code&gt;&amp;lt;example&amp;gt; Strip(&amp;quot;aba&amp;quot;,&amp;quot;ab&amp;quot;) return &amp;quot;a&amp;quot; &amp;lt;/example&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;移除不需要的註解，例如日誌、署名、重複資訊、被註解掉的 code 之類&lt;/li&gt;
&lt;li&gt;使用註解標籤：Todo = 待辦事項； Fixme = 需修復；可搭配 VS 內建的&lt;a href=&#34;https://reurl.cc/rl1Kab&#34;&gt;工作清單&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;附錄型別&#34;&gt;附錄：型別&lt;/h3&gt;
&lt;h4 id=&#34;實質型別&#34;&gt;實質型別&lt;/h4&gt;
&lt;p&gt;包含所有的數字型別、布林、字元和日期，以及結構和列舉&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;傳值&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;不允許 Null （除非特別指派 Nullable）&lt;/li&gt;
&lt;li&gt;有隱含的預設建構式，不需要用 new&lt;/li&gt;
&lt;li&gt;初始化之後才能使用&lt;/li&gt;
&lt;li&gt;資料傳遞時是將&lt;strong&gt;資料&lt;/strong&gt;複製到另一個變數&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;參考型別&#34;&gt;參考型別&lt;/h4&gt;
&lt;p&gt;能進一步分為陣列和類別型別，其中包含字串、所有陣列、類別、委派和介面。以及匿名和通用型別&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;傳址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;必須要使用 new 建立實體，並將該實體的參考記在變數&lt;/li&gt;
&lt;li&gt;資料傳遞時是將&lt;strong&gt;參考&lt;/strong&gt;複製到另一個變數&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;註記：&lt;strong&gt;字串比較特別&lt;/strong&gt;，實際上比較像一個結構。可以試著當成字元的串列比較好記，可以從 &lt;code&gt;char[] letters = {&#39;H&#39;,&#39;e&#39;,&#39;l&#39;,&#39;l&#39;,&#39;o&#39;};&lt;/code&gt; 和 &lt;code&gt;string a = new string(letters);&lt;/code&gt; 的實驗中感覺到一絲端倪。字串建立後也不允許直接變更，可能是藉此閃避一些更改時的問題。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h4 id=&#34;轉型&#34;&gt;轉型&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;隱含轉換&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;屬於安全的轉換，所以不用使用特殊語法&lt;/li&gt;
&lt;li&gt;小型到大型整數型別的轉換（如 int -&amp;gt; long）&lt;/li&gt;
&lt;li&gt;衍生型別 (Derived Class) 到基底型別 (Base Class) 的轉換&lt;/li&gt;
&lt;li&gt;例如 &lt;code&gt;int num = 2147483647; long bigNum = num;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;明確轉換&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;資料可能在轉換中遺失或失敗時，需要使用轉型運算子&lt;/li&gt;
&lt;li&gt;往精確度較低或範圍較小的型別進行的數值轉換（如 double -&amp;gt; int）&lt;/li&gt;
&lt;li&gt;基底類別往衍生類別的轉換&lt;/li&gt;
&lt;li&gt;例如 &lt;code&gt;double x = 1234.7; int y= (int)x&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用 Helper 類別轉換&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;在不相容型別間進行轉換時使用（如 int -&amp;gt; System.DateTime）&lt;/li&gt;
&lt;li&gt;使用 Helper 進行轉換（如 Int32.Parse），例如
&lt;ul&gt;
&lt;li&gt;System.BitConverter 類別&lt;/li&gt;
&lt;li&gt;System.Convert 類別&lt;/li&gt;
&lt;li&gt;內建數字型別 (Numeric Type) 的 Parse 方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;例如 &lt;code&gt;string x = &amp;quot;2016-04-08&amp;quot;; DateTime z = System.DateTime.Parse(x)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Pocket —— 稍後閱讀，想看再看</title>
      <link>https://igouist.github.io/post/2020/07/pocket/</link>
      <pubDate>Sat, 18 Jul 2020 10:17:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/pocket/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;備註： Pocket 將在 2025/07/08 下線（&lt;a href=&#34;https://x.com/Pocket/status/1925587573638144196&#34;&gt;官方推文&lt;/a&gt;），我個人現在也已經改用 Readwise Reader，有興趣的朋友可以參考 =&amp;gt; &lt;a href=&#34;https://readwise.io/read&#34;&gt;Readwise io&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在兩個多月前，我們介紹過將文章用 RSS 訂閱集中起來的工具 &lt;a href=&#34;https://igouist.github.io/post/2020/04/feedly/&#34;&gt;Feedly&lt;/a&gt;。但是，有些時候雖然對文章挺有興趣的，但並&lt;strong&gt;不適合馬上看&lt;/strong&gt;（例如在公司或學校的時候，看到社論、科技新聞等等）；或是像我個人平常休息時逛逛一些論壇或是文檔，這時候如果遇到一些&lt;strong&gt;比較長的、主題式的文章&lt;/strong&gt;，就會想要把文章存放起來，等晚些時候再看。&lt;/p&gt;
&lt;p&gt;雖然 Feedly 也有提供 Read Later 的功能，只要勾個標籤就可以之後再到 Read Later 的頁面去觀看。但懶惰如我，就會想要把所有稍後再看的文章集中在一塊，因此必須尋找一個前述場景都共用的做法，這時我就遇上了 Pocket。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://getpocket.com/&#34;&gt;Pocket&lt;/a&gt; 是一個簡單直接的「稍後閱讀」服務，操作方便，只需要擴充套件或分享，和一段能靜下來好好閱讀的時間即可服用&lt;/strong&gt;。在開始介紹之前，有幾件事必須報告：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果你和我一樣可能變成倉鼠型玩家，看到什麼都想加到稍後閱讀，可能要當心。建議可以先閱讀一下 &lt;a href=&#34;https://www.playpcesor.com/2011/10/blog-post.html&#34;&gt;用「稍後閱讀」過濾資訊洪水的五種層次與六個方法 - 電腦玩物&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;就像在 Feedly 介紹中提到的：這類工具是用來幫助我們完成&lt;strong&gt;被動接收資訊＋主動篩選資訊&lt;/strong&gt;的，莫要忘記原則，否則真的很容易看不完看到超載，心會很累。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你是 Android 或 iOS 的使用者，由於 Pocket 有提供網頁和 APP 等平台，但本篇以電腦的操作為主，使用手機操作的朋友可以參考 &lt;a href=&#34;https://www.kocpc.com.tw/archives/266659&#34;&gt;Pocket 稍後閱讀 APP，收藏想看的文章和網頁！&lt;/a&gt; 這篇的說明。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你已經決定要尋找稍後閱讀功能的工具，可以參考 &lt;a href=&#34;https://pickydigest.com/productivity/read-it-later-bookmark-app-competition/&#34;&gt;10個稍後閱讀書籤服務大評比！Pocket、Instapaper、Diigo還有更多！&lt;/a&gt;本篇並不會提到別的工具。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;介紹&#34;&gt;介紹&lt;/h2&gt;
&lt;p&gt;首先，我們得先到 &lt;a href=&#34;https://getpocket.com/&#34;&gt;Pocket&lt;/a&gt; 申請一個帳號，我個人是直接使用 Google 帳號做登入。成功登入之後應該能到 Pocket 的大廳：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JHo0YQh.webp&#34;
  alt=&#34;&#34;width=&#34;1258&#34; height=&#34;770&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們之後儲存的文章就會出現在這裡。上方工具列還有探索的功能，但我個人試過之後大多都是英文文章，對我這種英文白癡派不上什麼用場。&lt;/p&gt;
&lt;p&gt;接著讓我們安裝 Chrome 的 擴充套件：&lt;a href=&#34;https://chrome.google.com/webstore/detail/save-to-pocket/niloccemoadcdkdjlinkgdfekeahmflj&#34;&gt;Save to Pocket&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;之後我們直接找個一篇文章做示範，然後讓我們按下擴充套件的 Pocket 按鈕…&lt;/p&gt;
&lt;p&gt;（如果是手機使用者，這邊就是把目標網頁分享給 Pocket App）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tjRP5o2.webp&#34;
  alt=&#34;&#34;width=&#34;399&#34; height=&#34;283&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;再回到 Pocket …&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SAlnwW4.webp&#34;
  alt=&#34;&#34;width=&#34;1300&#34; height=&#34;792&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;就成功把文章存下來啦！&lt;/p&gt;
&lt;p&gt;點進去之後可以發現，它和 Notion、Evernote 那些筆記軟體的擷取方式一樣，是&lt;strong&gt;將文章內容擷取出來&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HwQbPRP.webp&#34;
  alt=&#34;&#34;width=&#34;1090&#34; height=&#34;885&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當然，作為一個閱讀服務，Pocket 服務該有的都有。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nyAVhSd.webp&#34;
  alt=&#34;&#34;width=&#34;1868&#34; height=&#34;888&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;畫面上方有一些基礎操作的工具列，例如替&lt;strong&gt;文章內容畫線、或是將文章加上分類標籤、進行封存和刪除等等&lt;/strong&gt;。此外，右上方也能調整閱讀版面的設定，但除了基本的&lt;strong&gt;主題色彩和字體大小&lt;/strong&gt;以外，更客製化的設定選項就必須付費才能取得了。&lt;/p&gt;
&lt;p&gt;（當然，如果像我這種有在使用 &lt;a href=&#34;https://chrome.google.com/webstore/detail/simpread-reader-view/ijllcpnolfcooahcekpamkbidhejabll/related&#34;&gt;簡閱&lt;/a&gt; 這類閱讀擴充套件的話，這部分就不會有什麼問題了XD）&lt;/p&gt;
&lt;p&gt;Pocket 作為稍後閱讀的暫存站，我個人是免費就用得很開心。如果有需要無限期存放文章或是無限標籤等需求，請再前往 &lt;a href=&#34;https://getpocket.com/premium?ep=10&#34;&gt;升級頁面&lt;/a&gt; 參考。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：Pocket 也有提供朗讀文章的功能，需要的時候可以用聽的，不過我個人沒有試過，因為遇到需要用聽的情景的話，我會跑去聽 Podcast。如果有聽文章習慣或是有興趣的朋友，可以參考&lt;a href=&#34;https://www.playpcesor.com/2016/02/pocket-reade-later.html&#34;&gt;[生活駭客3] 開始「聽」文章，日常時間加一倍的魔法&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;稍後閱讀功能的服務還挺多的，我之所以最後選定使用 Pocket，有一大半原因是因為我發現它&lt;strong&gt;能和我的 Kobo 電子書閱讀器同步&lt;/strong&gt;。作為一個通勤搭車每天動輒一兩小時的上班族，搭車時間拿來看看書看看文章真是再愜意不過了。但由於我真的很懶，捷運又常常站著，書還是挺重的不方便帶來帶去，所以後來我還是毅然決然踏入閱讀器的世界，這時候我才發現了 Pocket。&lt;/p&gt;
&lt;p&gt;利用 Pocket 我就能把早上從 Feedly 看見的部分文章，挪到上下班搭車的時候再用輕便的電子書閱讀，豈不妙哉。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/cIkoRR1.webp&#34;
  alt=&#34;&#34;width=&#34;1706&#34; height=&#34;960&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;示意圖（單純只是想秀一下閱讀器）&lt;/p&gt;
&lt;p&gt;利用 Pocket 我就能把想讀的文章同步到網頁、APP 和閱讀器等各平台，並利用碎片時間來看個一兩篇，既分散了有時遇到太多篇想看的文章的那種壓迫，瑣碎時間也不用再煩惱要幹嘛就能順手看一下，對我這種懶得想當下要幹嘛的人，真的是挺有幫助啊。&lt;/p&gt;
&lt;h2 id=&#34;結語&#34;&gt;結語&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;所有的工具都是為了完成整體流程的某個環節而存在&lt;/strong&gt;，凡如開發功能、佈 CI/CD 環境、或是專案管理具等等，我們無不利用各項工具和技術的搭配來組合成我們需要的流程。當然，資訊篩選也是如此，Pocket 提供了延後閱讀的服務，但這個服務的使用方式取捨於個人的習慣。如果能像電腦玩物說的，在把文章丟進稍後閱讀時，能先想一想「&lt;strong&gt;這則資訊我真的想要認真讀它嗎？&lt;/strong&gt;」如果是，那麼我就相信 Pocket 一定能夠幫上忙。&lt;/p&gt;
&lt;p&gt;我個人現在的習慣方式是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;從 &lt;strong&gt;Feedly&lt;/strong&gt; 取得篩選來源的文章，或是有空閒時從常用的幾個論壇和網站，從中挑選有興趣的讀&lt;/li&gt;
&lt;li&gt;若是當天有興趣的太多，或是文章過長不適合當下閱讀，就利用 &lt;strong&gt;Pocket&lt;/strong&gt; 暫存起來&lt;/li&gt;
&lt;li&gt;閱讀後，將認為有價值或喜歡的文章存放到 &lt;strong&gt;Notion&lt;/strong&gt; 並分類&lt;/li&gt;
&lt;li&gt;需要的時候，例如用到相關技術或想分享文章給朋友，就從 &lt;strong&gt;Notion&lt;/strong&gt; 中的分類提取文章出來&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;藉由這些工具組成了一個每日固定的閱讀流程，並穩定地增加庫存（對我本質還是倉鼠型），目前為止我都還覺得挺不錯的，這邊分享給各位。&lt;/p&gt;
&lt;p&gt;最後的最後，因為真的覺得&lt;strong&gt;電子書很讚&lt;/strong&gt;，還是想推廣一下。如果有對電子書有興趣的朋友，可以看一下以下這兩篇，整理得相當不錯，優缺點都有列到，尤其是閱讀前哨站那篇的 Q&amp;amp;A 整理部份已經涵蓋最常遇到的問題，推薦在考慮入坑時讀個一遍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://readingoutpost.com/ebook-ereader-experience/&#34;&gt;2年讀100本書的全職科技人，電子書閱讀器使用心得與感想&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aronhack.com/%E8%B3%BC%E8%B2%B7%E9%9B%BB%E5%AD%90%E6%9B%B8%E9%96%B1%E8%AE%80%E5%99%A8%E5%89%8D-%E5%BF%85%E8%AE%80%E5%84%AA%E7%BC%BA%E9%BB%9E%E5%8F%8A%E5%BF%83%E5%BE%97%E5%88%86%E4%BA%AB/&#34;&gt;購買電子書閱讀器前，必讀優缺點及心得分享&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那麼今天的分享就到這裡，如果有不錯的工具或是建議，也歡迎不吝提出。那麼，我們下周見！&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2011/10/blog-post.html&#34;&gt;用「稍後閱讀」過濾資訊洪水的五種層次與六個方法 - 電腦玩物&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2012/04/pocket-read-it-later.html&#34;&gt;Pocket Read it later 稍後閱讀同步、離線、免費行動口袋 - 電腦玩物&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2016/02/pocket-reade-later.html&#34;&gt;[生活駭客3] 開始「聽」文章，日常時間加一倍的魔法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pickydigest.com/productivity/read-it-later-bookmark-app-competition/&#34;&gt;10個稍後閱讀書籤服務大評比！Pocket、Instapaper、Diigo還有更多！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.kocpc.com.tw/archives/266659&#34;&gt;Pocket 稍後閱讀 APP，收藏想看的文章和網頁！&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (7): 介面</title>
      <link>https://igouist.github.io/post/2020/07/oo-7-interface/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:07 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-7-interface/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9eGbMlk.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果說&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;繼承&lt;/a&gt;是用來表明物件「屬於什麼」；那麼介面就是用來表明物件「能做什麼」。&lt;/p&gt;
&lt;p&gt;如果說&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;封裝&lt;/a&gt;是將物件視作一個整體，是隱藏複雜度；那麼介面就是封裝精神的體現。&lt;/p&gt;
&lt;p&gt;如果說&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;多型&lt;/a&gt;是指藉著繼承後能實作不同的行為的可能性達到擴展的彈性；那麼介面就是在實作多型。&lt;/p&gt;
&lt;p&gt;介面就是這麼厲害，這麼瀟灑。介面就是我大哥，今天誰不服介面，對不起！我們不認識。&lt;/p&gt;
&lt;p&gt;介面就像是針對類別的實作、物件的行為去做規定的一個契約書，會先定義好要實作這個介面的類別所必須要有的方法，而當我們建立符合這個介面的類別時，就必須實作出所有介面中定義好方法才可以。……這樣說起來實在太繞口，總而言之介面的核心概念只有一條：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我不在乎你是誰，我只在乎你能做什麼。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;還是公司誠徵工程師的例子&#34;&gt;還是公司誠徵工程師的例子&lt;/h2&gt;
&lt;p&gt;由於介面基本上就是封裝繼承多型抽象之大雜燴，所以我們把前面&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;多型&lt;/a&gt;的小明小華例子稍微修改來用吧。也就是以公司徵人的方式去理解介面。&lt;/p&gt;
&lt;p&gt;介面就像是老闆開出來的要求列表，例如說：要會寫 C#、要會寫 SQL、要會 VB…等等，於是老闆就貼出了徵人啟示，要求新來的員工必須要有 &lt;code&gt;IProgrammer&lt;/code&gt; 寫的能力：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IProgrammer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteCSharp();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteSQL();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteVB();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;特別注意和前面多型的例子的不同處，&lt;strong&gt;介面只需要先定義好該做的事，裡面怎麼做不需要管；所以只需要宣告要求的方法，不需要撰寫方法本體&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;於是今天小華就又(?)來面試了，但是他其實並不會寫 C#：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Hua&lt;/span&gt; : IProgrammer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Error: Hua 未實作 IProgrammer.WriteCSharp()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteSQL() { &lt;span style=&#34;color:#75715e&#34;&gt;/* Work */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteVB() { &lt;span style=&#34;color:#75715e&#34;&gt;/* Work */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這時候編譯器就會跳出錯誤了：很抱歉，你不符合我們 &lt;code&gt;IProgrammer&lt;/code&gt; 的規定，因為我們只喜歡訓練精英（略），請你實作完之後再來。否則你就不能掛上我們 &lt;code&gt; : IProgrammer&lt;/code&gt; 的頭銜。&lt;/p&gt;
&lt;p&gt;雖然小華面試失敗了，不過至少小華幫我們示範了一件事：類別要標上介面的方法就和繼承一樣，在類別名稱後面加上 &lt;code&gt;:&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;而小華走了之後，馬上就輪到小明開開心心地來應徵了，他不只會寫 C#、SQL 和 VB，甚至還會泡英式奶茶：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ming&lt;/span&gt; : IProgrammer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteCSharp() { &lt;span style=&#34;color:#75715e&#34;&gt;/* Work */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteSQL() { &lt;span style=&#34;color:#75715e&#34;&gt;/* Work */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; WriteVB() { &lt;span style=&#34;color:#75715e&#34;&gt;/* Work */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Tea MakeTea() { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Tea(teaName: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MilkTea&amp;#34;&lt;/span&gt;); }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;火速通過面試之後，老闆就讓小明上工了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IProgrammer programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在這個時候，小明已經不再只是小明，對老闆而言他就是一個&lt;strong&gt;符合應徵要求的工程師&lt;/strong&gt;，這裡只需要「&lt;strong&gt;我（老闆）要求能做這些事的人&lt;/strong&gt;」而不是「小明」，是 &lt;code&gt;IProgrammer&lt;/code&gt; 而不是 &lt;code&gt;Ming&lt;/code&gt;。在呼叫 &lt;code&gt;IProgrammer programmer = new Ming()&lt;/code&gt; 的同時，這裡就只剩下一個無情的寫程式機器，再也沒有小明。&lt;/p&gt;
&lt;p&gt;上面這句雖然和多型範例的說明九成一樣，但絕對不是我偷懶（真的），差異的地方就是介面的本質。可以稍加對照看看。&lt;/p&gt;
&lt;p&gt;當然，小明仍然不准在上班時間泡英式奶茶。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IProgrammer programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.MakeTea(); &lt;span style=&#34;color:#75715e&#34;&gt;// Error: IProgrammer 未包含 MakeTea 的定義&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;多型的時候，這個原因是由於今天的小明是工程師，工程師不需要會泡奶茶。也就是說子類別替代父類別時不需要那些父類別不會的動作。此處也是一樣的精神，我們要的是一個會寫程式的工程師，&lt;strong&gt;不是介面規範上的東西我們不需要&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這是達成關注點分離和職責分離必經的道路，習慣之後對於公開方法和私有方法也會有更進一步的心得，以公開方法實現介面的要求，配合私有方法拆解內部的複雜度，甚至&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;組合多個介面&lt;/a&gt;讓類別具有多項能力，這個過程只能用舒爽來形容（前提是介面不要開得太爛的話啦）。&lt;/p&gt;
&lt;h2 id=&#34;變更的彈性&#34;&gt;變更的彈性&lt;/h2&gt;
&lt;p&gt;介面作為特性的體現，更多的是概念上的東西。最後這一小段就讓我們聊聊這些部分。&lt;/p&gt;
&lt;p&gt;在新訓時改變我想法最多的就是介面，在這之前我只會一個函式硬寫到底，而習慣從介面開始設計物件後，才開始從功能的角度去想要怎麼寫。&lt;/p&gt;
&lt;p&gt;介面不同於前面各項特性是告訴我們物件應該有什麼特徵，而是要求我們用「不同功能的物件之間對接時，我們該怎麼處理」的角度去看待問題。&lt;/p&gt;
&lt;p&gt;介面的核心概念在於提供了更多的彈性，更精確地說是&lt;strong&gt;變更的彈性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原本是連線到 MySQL 取得資料，哪天突然就必須更改成要連線到 MongoDB 取得資料；&lt;/p&gt;
&lt;p&gt;原本是只要實作出使用者儲存訂單的操作，突然接到指令說使用者必須區分成一般使用者和尊爵用戶並且實作出不同的操作流程等等。&lt;/p&gt;
&lt;p&gt;變更總是來得又急又快，而這也讓我們靜下來想，當我們把關注點放在整個邏輯的時候－－&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我需要的是「連線到 MySQL 取得資料」的工具嗎？並不是，只要是「能連線到資料庫、能取得資料」就好了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我需要的是「以ＯＯＯ技術替使用者建立訂單並儲存」的工具嗎？並不是，「替使用者建立訂單並儲存」才是最重要的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;資料庫是可以替換的，儲存訂單到資料庫的工具也是可以替換的，甚至替使用者建立訂單的過程也是可以替換的。&lt;/p&gt;
&lt;p&gt;因此，從介面開始設計時最重要的是釐清「&lt;strong&gt;我需要的是什麼&lt;/strong&gt;」，用介面定義一份契約，把使用對象和實作銜接起來；並且把實作隔離成可替換的部件，達到解除&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;耦合&lt;/a&gt;的目標。&lt;/p&gt;
&lt;p&gt;最後還是回到了關注點分離的議題：主流程、商業邏輯專心做好自己的事，他們只需要知道這個物件能夠提供拿到資料的方法就好。而實際上怎麼拿到資料，則由實作的物件內部去處理，也就是封裝的核心精神。&lt;/p&gt;
&lt;p&gt;要理解介面的概念，訣竅在於把目光更集中在「功能」的角度。我們在理解物件的時候，可以知道冰箱是一個物件、冰櫃是一個物件、保冷袋是一個物件；但當我們在海邊釣到魚，想要找個地方保存的時候，我們需要的是冰箱嗎？是冰櫃嗎？是保冷袋嗎？&lt;/p&gt;
&lt;p&gt;都不是，我們需要的是「能低溫保存食物的東西」而已，今天你能用冰箱從海邊運到你家也沒關係，只要你實作得出來，並且魚是新鮮的就好。於是我們把&lt;strong&gt;觀看物件的角度集中在它的功能上&lt;/strong&gt;，我們針對我們的需求去定義好我們需要的功能，這就是介面。&lt;/p&gt;
&lt;p&gt;我們定義好什麼叫做飛行，於是鴿子跟烏鴉都算是實作了飛行；我們定義好什麼叫做游泳，於是海豚跟鯨魚都算是實作了游泳。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;只要你符合我需要的功能，達到我要的目的，不論你是誰，你如何實作，我都無所謂。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如此我們既達到了關注點分離，也保留了定好規則，將來可以使用不同實作的彈性；甚至將來接手的是另一個人，他看你的介面就能知道如何替換，替換時對象至少要能做到哪些事，今天他接到需求是上頭覺得保冷袋太 Low 了，我們要改用冰櫃車，他也有個接口／介面去指示他修改的方向。&lt;/p&gt;
&lt;p&gt;而在兩個系統，或是兩個分層之間要介接的時候，只需要提供我這個功能需要的接口／介面給對方，就能讓對方知道他必須實作哪些功能，如果我們要把運魚的需求託付給貨運公司，他看介面就知道我們要的是「低溫保存食物」，便可以提供對應的服務／實做給我們。如此豈不美哉！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：一些有寫過前後端銜接的，或是做過一些小工具的到這邊可能會覺得有點熟悉。例如說：程式和使用者銜接的點，叫做使用者介面；前端跟後端交換資料的 API，叫做應用程式介面。所謂的介面／接口就是這麼一回事，這裡也不例外。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;到這邊我們就介紹完介面的部分了，希望各位朋友能夠大概感覺到介面的精神。&lt;/p&gt;
&lt;p&gt;當然有些讀者看到這裡可能也會有點疑惑：&lt;/p&gt;
&lt;p&gt;像上面的例子中 &lt;code&gt;IProgrammer programmer = new Ming();&lt;/code&gt; 當我們宣告的當下不就還是知道了我們實作的對象是 &lt;code&gt;Ming&lt;/code&gt; 了嗎？這樣並沒有完全分離呀？（&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;依賴注入&lt;/a&gt;熱身中）&lt;/p&gt;
&lt;p&gt;等等諸如此類一堆問題，都也是我有過的想法，在接下來的新訓系列也將會逐漸說明，欲知後續如何，且待&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;下回&lt;/a&gt;分曉！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10229700&#34;&gt;設計模式起手式：介面（Interface）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ChunYeung/%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E4%B8%AD%E7%9A%84%E4%BB%8B%E9%9D%A2%E8%88%87%E6%8A%BD%E8%B1%A1%E9%A1%9E%E5%88%A5%E6%98%AF%E4%BB%80%E9%BA%BC-1199804ccc5f&#34;&gt;物件導向中的介面與抽象類別是什麼 ?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://benyi.logdown.com/posts/2018/02/11/oop-what-is-interface&#34;&gt;[物件導向] 何謂介面（interface）？ - Benyi Hsia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ad57475747/c-%E9%9B%9C%E8%A8%98-%E4%BB%8B%E9%9D%A2-interface-%E6%8A%BD%E8%B1%A1-abstract-%E8%99%9B%E6%93%AC-virtual-%E4%B9%8B%E6%88%91%E8%A6%8B-dc3c5878bb80&#34;&gt;C#雜記 — 介面(interface)、抽象(abstract)、虛擬(virtual)之我見 - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://exfast.me/2016/09/c-sharp-use-interface-abstract-override-inheritance-implements-a-simple-example/&#34;&gt;[C#] 利用 interface(介面) abstract(抽象) override(覆寫) inherit(繼承) 實作簡單範例 - 從入門到放棄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/initials/2016/06/18/102618&#34;&gt;[心得整理] c# 物件導向程式 - 3.抽象與介面 interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-4_%E6%8A%BD%E8%B1%A1%E9%A1%9E%E5%88%A5abstract%E8%88%87%E4%BB%8B%E9%9D%A2interface/&#34;&gt;Object Oriented物件導向-4:抽象類別(Abstract)與介面(Interface) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;推薦系列文&#34;&gt;推薦系列文&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (6): 抽象、覆寫</title>
      <link>https://igouist.github.io/post/2020/07/oo-6-abstract-override/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:06 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-6-abstract-override/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Y2gHFsr.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：這邊的抽象是指程式語言中的抽象類別，而非抽象化&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;抽象的概念很直接，請回想一下前面的例子就可以了：&lt;/p&gt;
&lt;p&gt;當我們在用卡牌的例子時，雖然怪獸卡跟魔法卡都繼承了 Card 這個類別，但是我們仍然能 &lt;br/&gt; &lt;code&gt;new Card()&lt;/code&gt; 來建立一張新卡牌，那…怪怪的吧，這張卡牌到底是什麼呀，空白的卡片嗎？&lt;/p&gt;
&lt;p&gt;又或是動物的例子，我們的狗跟貓都繼承了哺乳類，那我們能實例化一個哺乳類嗎？我們的狗跟鳥都是動物，那我們能實例化一個動物嗎？&lt;/p&gt;
&lt;p&gt;小明跟小華都繼承了工程師，那我們能 new 一個工程師嗎…？&lt;/p&gt;
&lt;p&gt;有些類別就是這樣，它們負責定義共通的那些特性，&lt;strong&gt;然而它們本身不應該被實體化成一個物件，這種類別我們就應該把它們標記為抽象類別&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;抽象類別在 C# 裡用 &lt;code&gt;abstract&lt;/code&gt; 這個修飾詞來表示&lt;/strong&gt;，可以加在類別或方法上。例如 &lt;code&gt;abstract class Animal&lt;/code&gt; 就代表動物這個類別是個抽象類別，它不能被實例化。&lt;/p&gt;
&lt;p&gt;而當加在方法上時，例如 &lt;code&gt;public abstract void Eat()&lt;/code&gt; 就是代表這個進食的方法無法被叫用，只能由繼承者去重新定義這個方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Animal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; color { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;abstract&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那麼繼承者們，也就是衍生類別如何去重新定義父類別的方法呢？&lt;/p&gt;
&lt;p&gt;&lt;del&gt;所謂「欲戴王冠，必 Override」&lt;/del&gt;，這時候就必須使用&lt;strong&gt;覆寫（&lt;code&gt;override&lt;/code&gt;）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;覆寫是指對於像是前述的抽象方法時，在同名的方法前加上 &lt;code&gt;override&lt;/code&gt; 關鍵字就可以讓程式知道你要覆寫這個方法（你不覆寫的話，編譯器還會生氣）。&lt;/p&gt;
&lt;p&gt;例如前述的 Eat，狗就可以用 &lt;code&gt;public override void Eat()&lt;/code&gt; 的方式去覆寫吃東西這個方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Dog&lt;/span&gt; : Animal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; color { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; } = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Black&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;/* 嚼嚼嚼 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但有時候我們只是希望秉持著多型的精神，讓子類別有可以重新定義的彈性，這時候我們就會使用&lt;br/&gt; &lt;strong&gt;虛擬（&lt;code&gt;virtual&lt;/code&gt;）&lt;/strong&gt; 的方式去標記這個方法，如此一來就可以實作，同時也讓子類別可以覆寫。&lt;/p&gt;
&lt;p&gt;例如可能狗有 &lt;code&gt;public virtual void Eat()&lt;/code&gt; 這個進食的方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Dog&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;/* 嚼嚼熱狗 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那假設我們有個 &lt;code&gt;Giwawa&lt;/code&gt; 繼承了 &lt;code&gt;Dog&lt;/code&gt;，但牠也是吃熱狗的，就可以選擇不去覆寫 &lt;code&gt;Eat()&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Giwawa&lt;/span&gt; : Dog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* 不打算實作 Eat，直接使用 Dog 類別的 Eat */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而當我們有了 &lt;code&gt;RobotDog&lt;/code&gt; 這個類別，它就可以繼承並且重新改寫掉 &lt;code&gt;Eat()&lt;/code&gt; 這個方法，從吃肉變成喝汽油。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RobotDog&lt;/span&gt; : Dog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;/* 嚼嚼汽油 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;除了使用 &lt;code&gt;override&lt;/code&gt; 去覆寫父類別的方法以外，也可以用 &lt;code&gt;new&lt;/code&gt; 去隱藏父類別的方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CyberDog&lt;/span&gt; : Dog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;/* 嚼嚼汽油 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;override&lt;/code&gt; 和 &lt;code&gt;new&lt;/code&gt; 的差別在於多型時轉型成父類別時的行為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;override&lt;/code&gt; 會直接取代掉父類別的方法，即使轉型為父類別還是以子類別的實作為主&lt;/li&gt;
&lt;li&gt;&lt;code&gt;new&lt;/code&gt; 則是會建立一個子類別專屬的方法，若轉型為父類別就會變回父類別的方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我們直接用例子來看看吧，假設我們現在有「拉不拉多」和「機器狗」，都繼承了「狗」。差別在於拉不拉多 override 了 &lt;code&gt;Eat()&lt;/code&gt; 這個方法，而機器狗 new 了 &lt;code&gt;Eat()&lt;/code&gt; 這個方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Dog&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat() 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    =&amp;gt; Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;吃了熱狗&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Labrador&lt;/span&gt; : Dog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat() 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    =&amp;gt; Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;吃了超大熱狗&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RobotDog&lt;/span&gt; : Dog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat() 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    =&amp;gt; Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;喝了超多汽油&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著讓我們來看看當他們被實例化之後，以及被轉型為父類別的時候的 &lt;code&gt;Eat()&lt;/code&gt; 有什麼不一樣吧：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Labrador =&amp;gt; override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; lala = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Labrador();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lala.Eat(); &lt;span style=&#34;color:#75715e&#34;&gt;// 吃了超大熱狗&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;((Dog)lala).Eat(); &lt;span style=&#34;color:#75715e&#34;&gt;// 吃了超大熱狗&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ==============================&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// RobotDog =&amp;gt; new &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; robot = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; RobotDog();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;robot.Eat(); &lt;span style=&#34;color:#75715e&#34;&gt;// 喝了超多汽油&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;((Dog)robot).Eat(); &lt;span style=&#34;color:#75715e&#34;&gt;// 吃了熱狗&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到使用 &lt;code&gt;new&lt;/code&gt; 來覆寫的 RobotDog 在被轉型為 Dog 的時候突然就變回吃熱狗了！&lt;/p&gt;
&lt;p&gt;要特別注意的是：當你覆寫了父類別的方法，卻忘記加上 &lt;code&gt;override&lt;/code&gt; 的話，默認會當成是要 &lt;code&gt;new&lt;/code&gt;，所以覆寫的時候還是小心一點，具體地把 &lt;code&gt;override&lt;/code&gt; 或 &lt;code&gt;new&lt;/code&gt; 寫出來吧！&lt;/p&gt;
&lt;p&gt;關於抽象和覆寫這部份的範例，因為我個人碰觸的比較少，唯恐我的舉例不夠深入，這邊再附上幾個不錯的範例，可以作為參考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ad57475747/c-%E9%9B%9C%E8%A8%98-%E4%BB%8B%E9%9D%A2-interface-%E6%8A%BD%E8%B1%A1-abstract-%E8%99%9B%E6%93%AC-virtual-%E4%B9%8B%E6%88%91%E8%A6%8B-dc3c5878bb80&#34;&gt;C#雜記 — 介面(interface)、抽象(abstract)、虛擬(virtual)之我見 - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://exfast.me/2016/09/c-sharp-use-interface-abstract-override-inheritance-implements-a-simple-example/&#34;&gt;[C#] 利用 interface(介面) abstract(抽象) override(覆寫) inherit(繼承) 實作簡單範例 - 從入門到放棄&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接著&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;下一篇&lt;/a&gt;，我們就接著看這一部分的最後一片拼圖：介面吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ad57475747/c-%E9%9B%9C%E8%A8%98-%E4%BB%8B%E9%9D%A2-interface-%E6%8A%BD%E8%B1%A1-abstract-%E8%99%9B%E6%93%AC-virtual-%E4%B9%8B%E6%88%91%E8%A6%8B-dc3c5878bb80&#34;&gt;C#雜記 — 介面(interface)、抽象(abstract)、虛擬(virtual)之我見 - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://exfast.me/2016/09/c-sharp-use-interface-abstract-override-inheritance-implements-a-simple-example/&#34;&gt;[C#] 利用 interface(介面) abstract(抽象) override(覆寫) inherit(繼承) 實作簡單範例 - 從入門到放棄&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2018/02/java-overload.html&#34;&gt;Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) - 菜鳥工程師肉豬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-2_%E5%BB%BA%E6%A7%8B%E5%BC%8Fconstructor%E5%A4%9A%E8%BC%89overloading%E8%88%87%E8%A6%86%E5%AF%ABoverriding/&#34;&gt;Object Oriented物件導向-2:建構式(Constructor)、多載(Overloading)與覆寫(Overriding) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/abstract&#34;&gt;Abstract - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/knowing-when-to-use-override-and-new-keywords&#34;&gt;了解使用 Override 和 New 關鍵字的時機 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (5): 多型</title>
      <link>https://igouist.github.io/post/2020/07/oo-5-polymorphism/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:05 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-5-polymorphism/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/65N5R3A.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;多型算是比較三特性之中給人感覺比較溫和的了，不如說只要有了繼承，那麼多型的到來就是必然的。多型的定義是：不同的物件能夠做出一樣的行為，但必須由他們自己的程式碼來實作。&lt;/p&gt;
&lt;p&gt;白話一點說就是：&lt;strong&gt;一樣的事，不同做法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;多型相對是比較好理解的，畢竟每個人做同一件事的方法本來就不太一樣。例如一樣是泡奶茶，英國就正常地泡，&lt;a href=&#34;https://www.ettoday.net/news/20200707/1754177.htm&#34;&gt;美國就會用微波爐&lt;/a&gt;；一樣是肉粽，有些人就是比較愛吃油飯；到了程式的世界裡也是，即使繼承了同一個物件，實現這個行為的方式也可以不同。&lt;/p&gt;
&lt;p&gt;在此可以先推菜鳥工程師肉豬的這篇 &lt;a href=&#34;https://matthung0807.blogspot.com/2018/02/java-overload.html&#34;&gt;Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism)&lt;/a&gt; 中的說明。其中可以從例子看到儘管狗跟鳥都是繼承自動物這個類別，但對於「移動」這個方法，他們實作的方式並不一樣。這就是多型的範例。&lt;/p&gt;
&lt;p&gt;我個人更喜歡用職位的方式去理解多型。&lt;/p&gt;
&lt;p&gt;被繼承者就像是該職位的工作，例如說：&lt;code&gt;Programmer&lt;/code&gt; 要會寫 C#、要會寫 SQL、要會 VB…等等，於是老闆就貼出了徵人啟示，要求新來的員工必須要有 &lt;code&gt;Programmer&lt;/code&gt; 寫的能力：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Programmer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteCSharp()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;/* 努力地寫 C# */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteSQL()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;/* 努力地寫 SQL */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;virtual&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteVB()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;/* 努力地寫 VB */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;補充說明：這邊用到的 &lt;code&gt;virtual&lt;/code&gt; 和 &lt;code&gt;override&lt;/code&gt; 這兩個關鍵字&lt;br/&gt;
我們會在下一篇的&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override/&#34;&gt;覆寫&lt;/a&gt;進行介紹。有興趣的朋友也可以先偷看一眼呦！&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;於是今天小華就來面試了，不過他寫的程式碼品質…不怎麼樣。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Hua&lt;/span&gt; : Programmer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteCSharp()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ShitCode&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteSQL()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ShitCode&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteVB()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ShitCode&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而小華應徵的隔天，小明也開開心心地來應徵了，他不只 C#、SQL 和 VB 都寫得很好，甚至還會泡英式奶茶：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ming&lt;/span&gt; : Programmer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteCSharp()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CleanCode&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteSQL()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CleanCode&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; WriteVB()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CleanCode&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Tea MakeTea() 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Tea(teaName: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MilkTea&amp;#34;&lt;/span&gt;); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;火速通過面試之後，老闆就讓小明上工了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Programmer programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp(); &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;CleanCode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp(); &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;CleanCode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp(); &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;CleanCode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;過了幾天後，老闆決定讓小明和小華一起寫同個專案：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; newProject()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Programmer programmer001 = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Programmer programmer002 = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Hua();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer001.WriteCSharp(); &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;CleanCode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer002.WriteCSharp(); &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;ShitCode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer001.WriteCSharp(); &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;CleanCode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer002.WriteCSharp(); &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;#34;ShitCode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這裡也就是多型的核心概念，&lt;strong&gt;用子類別實作出各式各樣不同的方法，藉此讓父類別的方法藉此達到延伸和多樣化的效果&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如說一樣是 &lt;code&gt;Programmer&lt;/code&gt; 的 &lt;code&gt;WriteCSharp()&lt;/code&gt; 這個方法，小明的實現就是 &lt;code&gt;return &amp;quot;CleanCode&amp;quot;;&lt;/code&gt; 而小華的實作方式則是 &lt;code&gt;return &amp;quot;ShitCode&amp;quot;;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;同樣地，最常被舉的例子就是動物。當有動物這個類別時，儘管狗跟貓都繼承了這個類別，但他們都可以對「叫聲」做出不同的實作。&lt;/p&gt;
&lt;p&gt;因為有了多型，動物這個父類別，就能夠藉由子類別來完成擴展。更進一步來說，藉由子類別的擴展，我們能夠&lt;strong&gt;讓父類別做出各式各樣的事，而不需要更動父類別本身&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最常碰到的例子就是資料庫連線。例如說，我們可以讓 &lt;code&gt;MySqlDBConnect&lt;/code&gt; 和 &lt;code&gt;MongoDBConnect&lt;/code&gt; 都繼承 &lt;code&gt;DBConnect&lt;/code&gt;，但各自保有對應不同資料庫的實作。&lt;/p&gt;
&lt;p&gt;如此一來，&lt;code&gt;DBConnect&lt;/code&gt; 就獲得了用不同方法連線到不同資料庫的擴展，得到了對應狀況靈活使用的彈性。同時，使用 &lt;code&gt;DBConnect&lt;/code&gt; 物件的其他物件也可以不用管現在的 &lt;code&gt;DBConnect&lt;/code&gt; 連線是哪個子類別來工作的，只要知道能夠連線並取得資料就好，也達到了以封裝降低耦合的要求。&lt;/p&gt;
&lt;p&gt;以上就是多型的核心。但當我們把子類別塞到父類別的殼裡面使用的時候，還需要注意：這時候的子類別已經是父類別的形狀了&lt;/p&gt;
&lt;p&gt;當我們&lt;strong&gt;將小明宣告成工程師&lt;/strong&gt;這一瞬間，小明已經不再只是小明，對老闆而言他就只是一個工程師，&lt;strong&gt;這裡只剩下「工程師」而不是「小明」&lt;/strong&gt;，是 &lt;code&gt;Programmer&lt;/code&gt; 而不是 &lt;code&gt;Ming&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;Programmer programmer = new Ming()&lt;/code&gt; 執行完畢的同時，這裡就只剩下一個無情的寫程式機器，再也沒有小明。&lt;/p&gt;
&lt;p&gt;當然，小明也不准在上班時間泡英式奶茶，我們不是請你來做這個的！&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Work()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Programmer programmer = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Ming();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.WriteCSharp();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    programmer.MakeTea(); &lt;span style=&#34;color:#75715e&#34;&gt;// Error: Programmer 未包含 MakeTea 的定義&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣只會讓編譯器尷尬地說：請你照我們契約書上面走好嗎？畢竟這裡只剩下 &lt;code&gt;Programmer&lt;/code&gt; 而不是 &lt;code&gt;Ming&lt;/code&gt; 了，一個無情的寫程式機器是不需要，也不會知道怎麼泡英式奶茶的。&lt;/p&gt;
&lt;p&gt;這邊我們就能知道：&lt;strong&gt;當子類別被以父類別的名義建立出來時，他就只能夠表現出父類別的樣子&lt;/strong&gt;。換句話說，我們宣告的是什麼，他就只會做什麼，但要怎麼做倒是沒關係。雖然是一個令人悲傷的故事，但為了遵守&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;封裝&lt;/a&gt;的精神，讓呼叫的物件不需要去了解是誰繼承、又由誰實作等等，為了物件界的秩序，這也是莫可奈何。&lt;/p&gt;
&lt;p&gt;最後再說一聲，大話設計模式用兒子代替爸爸上台表演京劇的例子實在舉得很不錯，有興趣的可以去看看，這例子很能表現出那種披著父親的皮，用著自己的技術，但遮著臉不能被發現的感覺。如此傳神！&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;到這邊三大特性就說明完了，雖然我說明的相當模糊籠統，但希望概念能夠傳達到。&lt;/p&gt;
&lt;p&gt;畢竟，就像我開頭引用的，我很喜歡這句「&lt;strong&gt;如果你問 100 個人這個問題，可能會得到 200 個答案，所以你一定要有自己獨到、有自信、精闢的見解或描述方式&lt;/strong&gt;。」像我女友，當我問他物件導向的時候，她（大致上）是這樣說明的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;封裝：醬包跟麵的工作都在泡麵工廠做完了，我們只要拿來泡就好&lt;/li&gt;
&lt;li&gt;繼承：我們可以買了泡麵之後，再自己加蛋加料&lt;/li&gt;
&lt;li&gt;多型：一樣是泡麵，實作出來的口味都不一樣&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;說得我都要去買一碗來煮了。但是，物件是為了貼近我們的現實世界，而每個人的世界觀本來就不一樣。&lt;strong&gt;你必須自己體會，然後才會有自己的觀點&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;至於那些說：你不是說要用卡牌當範例，怎麼突然多了個工程師小明小華？我只能說抱歉，洗澡的時候突然想到的，不寫不舒服。卡牌就想到再補吧，耶嘿。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;下一篇&lt;/a&gt;開始就要進入抽象等等更模糊籠統的部分了，希望還能穩住哪。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/initials/2016/06/10/171117&#34;&gt;[心得整理] c# 物件導向程式 - 2.封裝、繼承、多型的三大特性 - 聊聊程式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@totoroLiu/%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-object-oriented-programming-%E6%A6%82%E5%BF%B5-5f205d437fd6&#34;&gt;物件導向(Object Oriented Programming)概念 - Po-Ching Liu - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-3_%E5%B0%81%E8%A3%9Dencapsulation%E7%B9%BC%E6%89%BFinheritance%E8%88%87%E5%A4%9A%E5%9E%8Bpolymorphism/&#34;&gt;Object Oriented物件導向-3:封裝(Encapsulation)、繼承(Inheritance)與多型(polymorphism)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2018/02/java-overload.html&#34;&gt;Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) - 菜鳥工程師肉豬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789866761799&#34;&gt;《大話設計模式》附錄：物件導向基礎&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;推薦系列文&#34;&gt;推薦系列文&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (4): 繼承</title>
      <link>https://igouist.github.io/post/2020/07/oo-4-inheritance/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:04 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-4-inheritance/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/g9FZ3WN.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著要介紹的是繼承 aka 物件導向三大特性之王 aka 濫用榜 Ko.1 ，繼承的強大幾乎和它的惡名一樣可怕，給一個從聊聊程式的這篇 &lt;a href=&#34;https://dotblogs.com.tw/initials/2016/06/10/171117&#34;&gt;[心得整理] c# 物件導向程式 - 2.封裝、繼承、多型的三大特性&lt;/a&gt; 摘過來的例子就可以略知一二了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IFV4xjr.webp&#34;
  alt=&#34;&#34;width=&#34;581&#34; height=&#34;548&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;什麼也不做，僅僅只是繼承而已，就取得了繼承對象（C# 中稱為基底類別）近乎全部的內容，真是太可怕了。在 C# 中，繼承可以取得基底類別除了 &lt;code&gt;Private&lt;/code&gt; 以外所有的內容，例如 &lt;code&gt;Protected&lt;/code&gt; 更是表明就是只給繼承使用的。&lt;/p&gt;
&lt;p&gt;由此可見，在減少重複程式碼的路上，繼承無疑達到了全新的高度。&lt;/p&gt;
&lt;p&gt;那麼繼承代表的是什麼意思呢？大多的網站都能直接說明：&lt;strong&gt;繼承是一種「is-a」的關係。當你能說出Ａ是一個Ｂ的時候，就代表你認為Ａ可以繼承自Ｂ&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最直覺的繼承例子就是&lt;strong&gt;物種的分類&lt;/strong&gt;。舉例來說，狗跟貓都是哺乳類，因此他們都可以繼承到一些哺乳類共通的特徵（例如哺乳、用肺呼吸）。藉由繼承，我們可以把這些哺乳類共有的特徵全部放在哺乳類這個物件，再由狗和貓分別去繼承哺乳類，藉此讓他們都能得到哺乳類的特徵，再進一步發展出自己的特徵和行為，甚至重新定義基底類別的方法為自己所用。因此，像大話設計模式就將繼承說明如：繼承者是對於被繼承者的一種特殊化。&lt;/p&gt;
&lt;p&gt;如此一來，當我們需要修改哺乳類的定義的時候，&lt;strong&gt;只需要修改一個地方，而繼承了哺乳類的這些物件（C# 中稱為衍生類別）全都能夠一起修改到，大大地減少了跑來跑去修改的次數，也讓程式碼的重複大幅度地減少&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;然而也因為如此，繼承最大的惡名出現了：&lt;strong&gt;繼承享受了取用基底類別內容的好處，卻也必須背負牽一髮動全身的風險&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;繼承的特性和封裝有天生的衝突。為了從封裝好的物件之中取得內容，減少程式碼的重複，我們有了繼承，然而這樣無疑破壞了基底類別的封裝，完整地暴露給了衍生類別，兩者之間形成了強&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;耦合&lt;/a&gt;的關係。&lt;/p&gt;
&lt;p&gt;對於衍生類別而言，它必須依賴著基底類別，倘若哪天基底類別的屬性變更了，例如型別或名稱有變動，那麼所有衍生類別使用到的地方都會受到影響，這時候在程式碼裡的修改規模，將會隨著繼承的濫用程度提升，達到一個相當龐大的地步。&lt;/p&gt;
&lt;p&gt;事實上，這是相當好理解的。我們藉由哺乳類去繼承出了狗科跟貓科兩個類別；那麼假設我們時光回溯，重新改變了哺乳類的演化過程，今天的哺乳類變成了三隻腳而且還有翅膀，那麼後面演化出來的狗跟貓又會怎麼樣呢？&lt;strong&gt;直接修改源頭，對後續的衍生者而言無疑是相當大的災難&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;同時由於繼承的方便和概念實在相當廣泛，因此也經常被胡亂使用。我個人就遇過專案之中，前人為了讓某個類別擁有各式各樣的方法，先後繼承了數學運算、連線至資料庫、畫面上的資料處理等等數個類別，形成一條既長又龐大的繼承鏈，最終達到了無法修改的地步。&lt;/p&gt;
&lt;p&gt;沒有人知道這個合成怪獸是來做什麼的，這種&lt;a href=&#34;https://en.wikipedia.org/wiki/God_object&#34;&gt;神之物件&lt;/a&gt;搖身一變就變成滅世主宰，實在是相當恐怖。&lt;/p&gt;
&lt;p&gt;因此對於繼承，前輩們通常只有一種叮囑：&lt;strong&gt;謹慎使用，或是乾脆不要用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;對於繼承的概念，這邊推薦可以看看，&lt;a href=&#34;https://milikao.pixnet.net/blog/post/543592&#34;&gt;到底誰該去繼承誰？ 物件導向初學者應該要知道的事情(三)&lt;/a&gt; 這篇從圓和橢圓的各種繼承方式切入，很仔細地講解了不同思路使用繼承遇到的問題，尤其是示範完直覺的做法之後展示經典的段落相當重要。&lt;/p&gt;
&lt;p&gt;另外，我們在後續的&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;里氏替換原則&lt;/a&gt;也會提到繼承需要注意的一些問題，此處暫且按下不表。&lt;/p&gt;
&lt;p&gt;那麼我們就回到卡牌的例子：&lt;/p&gt;
&lt;p&gt;假使我們的卡牌現在有了功能卡，這類卡牌在遊戲王叫做魔法卡，而在爐石稱之為法術，雖然這也是一種卡片，但和前面提過的戰士和怪獸等等顯然完全不同。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MagicCard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost; &lt;span style=&#34;color:#75715e&#34;&gt;// 資源花費&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Magic Effect; &lt;span style=&#34;color:#75715e&#34;&gt;// 法術效果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; MagicCard() { &lt;span style=&#34;color:#75715e&#34;&gt;/* 建構式 */&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#75715e&#34;&gt;/* set; get; */&lt;/span&gt;} 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;魔法卡並沒有攻擊力和生命值，只有對應的法術效果。同時，我們發現卡牌有資源花費的需要，像是爐石戰記或是殺戮尖塔這類有資源的遊戲，打出卡片的時候會需要花費水晶等資源，藉此限制玩家一回合內能使用的策略。&lt;/p&gt;
&lt;p&gt;現在我們明顯可以發現兩個問題：這兩個種類的卡片，都是卡片呀！而且，內容有一半都是重複的。這是我們該使用繼承的時機了。&lt;/p&gt;
&lt;p&gt;首先我們將原本的卡片更改為 怪獸卡。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MonsterCard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; MonsterCard() { &lt;span style=&#34;color:#75715e&#34;&gt;/* 建構式 */&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#75715e&#34;&gt;/* set; get; */&lt;/span&gt;} 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Hit(MonsterCard target) { &lt;span style=&#34;color:#75715e&#34;&gt;/* 一些痛揍其他怪獸卡的方法 */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們開始設計基底類別：卡片。我們可以觀察到，怪獸卡和魔法卡相同的部分有：名稱、敘述和卡片花費。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#75715e&#34;&gt;/* set; get; */&lt;/span&gt;} 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;將原本的怪獸卡和魔法卡改成繼承自卡片類別，並且將重複的部份移除，直接取用基底類別的內容就好。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MonsterCard&lt;/span&gt; : Card
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; MonsterCard() { &lt;span style=&#34;color:#75715e&#34;&gt;/* 建構式 */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Hit(MonsterCard target) { &lt;span style=&#34;color:#75715e&#34;&gt;/* 一些痛揍其他怪獸卡的方法 */&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MagicCard&lt;/span&gt; : Card
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Cost;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Magic Effect;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; MagicCard() { &lt;span style=&#34;color:#75715e&#34;&gt;/* 建構式 */&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到我們在 C# 的繼承方式是使用 &lt;code&gt;類別 : 基底類別&lt;/code&gt; 的方式來宣告。並且也能發現，怪獸卡的內容變簡潔了。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; goblin = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MonsterCard(name: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;哥布林&amp;#34;&lt;/span&gt;, attack: &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, health: &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; warrior = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MonsterCard(name: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;戰士&amp;#34;&lt;/span&gt;, attack: &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, health: &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;warrior.Description = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;他是一個專殺哥布林的戰士！&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;warrior.Hit(goblin);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;並且在使用上也沒有任何差錯，我們仍然能給予怪獸卡名字和敘述。&lt;/p&gt;
&lt;p&gt;當然在實際的卡牌遊戲中，魔法卡還能細分出更多種類，因此魔法卡類別還能再被一些更細的分類，例如指向法術等等去繼承，形成如同樹狀的繼承關係，如同物種演化一般。&lt;/p&gt;
&lt;p&gt;繼承的段落也快結束了，這邊再次叮嚀一番：&lt;strong&gt;除非你很確定，否則請不要使用繼承&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;繼承帶來了相當大的好處，減少的重複程式碼量號稱三特性之冠；但同時他帶來的後果也是最嚴重的，堪稱三特性中的擊墜之王，鏖殺了數以萬計濫用和誤用的工程師…和維護他們系統的工程師，不可不慎。&lt;/p&gt;
&lt;p&gt;但如果已經看到了這裡，還請你先記著繼承的概念，在不遠處的將來你將會遇到他那不太像又有點像的兄弟：介面。這邊就先打住。&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;下一篇&lt;/a&gt;就讓我們繼續來看三特性的末席：多型吧。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10204633&#34;&gt;不要造神 (神一般的物件) - 可不可以不要寫糙 code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://milikao.pixnet.net/blog/post/543592&#34;&gt;到底誰該去繼承誰？ 物件導向初學者應該要知道的事情(三)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ChunYeung/%E4%BB%80%E9%BA%BC%E6%98%AFoo-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%88%87%E7%B9%BC%E6%89%BF-6955239576af&#34;&gt;什麼是OO？物件導向與繼承 - Chun Yeung - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/initials/2016/06/10/171117&#34;&gt;[心得整理] c# 物件導向程式 - 2.封裝、繼承、多型的三大特性 - 聊聊程式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@totoroLiu/%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-object-oriented-programming-%E6%A6%82%E5%BF%B5-5f205d437fd6&#34;&gt;物件導向(Object Oriented Programming)概念 - Po-Ching Liu - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/tutorials/inheritance&#34;&gt;C# 和 .NET 中的繼承 - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789866761799&#34;&gt;《大話設計模式》附錄：物件導向基礎&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;推薦系列文&#34;&gt;推薦系列文&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (3): 封裝</title>
      <link>https://igouist.github.io/post/2020/07/oo-3-encapsulation/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:03 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-3-encapsulation/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/cc9DLDo.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;封裝包含了兩個重要的觀念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;控制物件和外部進行互動的出入口&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隱藏物件內部的細節資訊&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;強者我同事整理的&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-3_%E5%B0%81%E8%A3%9Dencapsulation%E7%B9%BC%E6%89%BFinheritance%E8%88%87%E5%A4%9A%E5%9E%8Bpolymorphism/&#34;&gt;文章&lt;/a&gt;裡的例子就舉得不錯：當你按下鍵盤的Ａ鍵，螢幕隨即出現了Ａ，你不必知道中間發生了什麼事，你只需要知道怎麼操作和最後得到什麼就可以了。&lt;/p&gt;
&lt;p&gt;其中鍵盤提供的按鍵，就是我們對電腦進行互動的出入口；而電腦實際上做了什麼事情，也被隱藏了起來，讓我們只需要關注結果就好。&lt;/p&gt;
&lt;p&gt;此外我也看到過販賣機的例子，&lt;strong&gt;當你去販賣機買飲料，你也不需要知道裡面的構造，只要知道你選了飲料投了錢，飲料就會跑出來就行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;從上面的兩個例子，相信大家已經掌握到封裝的概念了：&lt;strong&gt;將物件視作一個整體，把內部的實作內容隱藏起來，讓使用者只需要知道怎麼使用這個物件即可。&lt;/strong&gt;（相似的思路，我們後續的&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;介面&lt;/a&gt;會再提到）&lt;/p&gt;
&lt;p&gt;如果封裝做得夠好，除了可以將程式碼整理得井井有條以外，也能讓物件內部的修改不會直接影響到使用物件的地方，達成了降耦合的目標&lt;/p&gt;
&lt;p&gt;並且也能讓物件的使用者直覺地知道如何使用物件提供的方法，如此使用者就可以專注在更高層次的抽象，而不用被物件內部的細節所干擾。&lt;/p&gt;
&lt;p&gt;最後，從上面的敘述中我們可以察覺到要實現封裝，最重要的就是：&lt;strong&gt;對外的開放程度（存取範圍）的控制&lt;/strong&gt;。或是套一句前輩的說法：&lt;strong&gt;給程式碼隱私的空間&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果想問「什麼是耦合？」的朋友，建議可以看看這篇：&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10203659&#34;&gt;實務上的高內聚與低耦合&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;或是參照本系列後續的 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;內聚與耦合&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;存取範圍與存取子&#34;&gt;存取範圍與存取子&lt;/h2&gt;
&lt;p&gt;先讓我們從存取範圍開始說起吧，因為我個人慣用的是 C#，因此就介紹一下 C# 是怎麼控制存取範圍的。&lt;/p&gt;
&lt;p&gt;在 C# 之中，類別裡控制可見度是使用修飾子來定義存取範圍，也就是當我們替類別宣告欄位時常看到的 &lt;code&gt;Public&lt;/code&gt; 和 &lt;code&gt;Private&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Public&lt;/code&gt;: 這是公開的，所有人都看得到&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Private&lt;/code&gt;: 這是私有的，只有自己看得到&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了最常用的這兩個以外，還有其他的修飾子可以先知道一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Protected&lt;/code&gt;: 這是受到保護的，只有自己和繼承的孩子們看得到&lt;/li&gt;
&lt;li&gt;&lt;code&gt;internal&lt;/code&gt;: 這是內部的，只有身為同一個組件的朋友們看得到&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Protected internal&lt;/code&gt;：組合上面兩個，也就是可以給同個組件的朋友們，或是其他組件繼承的孩子們看見&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下來的部分會以最常見的 &lt;code&gt;Public&lt;/code&gt; 和 &lt;code&gt;Private&lt;/code&gt; 來繼續說明，對存取範圍的這些修飾子有興趣的朋友，可以參照 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/accessibility-levels&#34;&gt;存取範圍層級&lt;/a&gt; 的說明。&lt;/p&gt;
&lt;p&gt;現在我們已經知道了有哪些修飾子可以用來控制存取範圍，但為什麼我們會需要宣告存取範圍的大小呢？其根本是為了&lt;strong&gt;將控制權掌握在物件本身&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;就像大話設計模式比喻的：物件就像間房子，我們不希望被看光光，可以看見的 &lt;code&gt;Public&lt;/code&gt; 就像門和窗，而不該看見的 &lt;code&gt;Private&lt;/code&gt; 則是用牆壁隱藏起來，而對於這間房子而言，門窗是可以控制的。&lt;/p&gt;
&lt;p&gt;對於這部分的範例，我覺得 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/using-properties&#34;&gt;微軟文件的範例&lt;/a&gt; 裡設定月份的區塊已經能很清楚表達了。但為了這篇文章的一致性，還是硬擠著一個範例出來：&lt;/p&gt;
&lt;p&gt;某一天，我們突然決定讓使用者可以傳入卡牌敘述了，但是卡牌上能顯示的字數有限，只能顯示 30 個字，因此首先我們先把卡牌敘述改成私有的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Level;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _description; &lt;span style=&#34;color:#75715e&#34;&gt;// 更改為私有的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* 一些其他方法 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;呃可能第一步就會讓人有些疑惑：「啊你要給人家傳東西進來還改私有？」但等等，且聽我娓娓道來：&lt;strong&gt;C# 中的屬性，是用 &lt;code&gt;Set&lt;/code&gt; 和 &lt;code&gt;Get&lt;/code&gt; 兩個方法去存取的&lt;/strong&gt;，又稱做&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/restricting-accessor-accessibility&#34;&gt;存取子&lt;/a&gt;。這兩個看門仔也就擔當了房屋的門窗、出入境時的海關、古代大戰中的關隘這類「控制進出通道」的角色。&lt;/p&gt;
&lt;p&gt;現在讓我們試著規劃出我們的門和窗，在上面的例子中，我們想要當卡牌的敘述進來時，保持在 30 個字：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 略&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; _description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt;.Length &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &amp;amp;&amp;amp; &lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt;.Length &amp;lt; &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._description = &lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; System.Exception(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;就跟你說限 30 個字看不懂喔！&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* 一些其他方法 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一來我們就能對存取屬性時的行為進行管控囉。&lt;/p&gt;
&lt;p&gt;那可能有些朋友會有疑惑：那為什麼我不能直接對外開放卡牌敘述，然後修改的時候檢查完再傳進來 set 就好了呢？這個就牽涉到一些「改太多地方了我要死啦」的悲情故事，這邊再舉個例子給大家體會一下。&lt;/p&gt;
&lt;p&gt;例如說，我們的卡片現在加入了戰力指數系統，這個戰力是預先從卡片的各項資訊計算好，並存放在資料庫的。而且因為計算的關係可能有小數點後十位之類的，那我們拿出來的時候可能會長這樣：&lt;code&gt;Power: 99.256256&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;而在建立類別的時候，也很自然地選用了 &lt;code&gt;double&lt;/code&gt; 來處理，於是現在類別就長這樣：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Power;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這個系統上線運行了一段時間之後，突然上頭來了需求：請把所有顯示到卡片敘述的地方都改成小數點後兩位就好。&lt;/p&gt;
&lt;p&gt;假設我們不能直接修改從資料表取出來時的數值（或是已經改了然後被前輩電）因為記 Log 或是什麼戰力對決(?)功能還會需要用到原本的戰力數值之類的理由，因此物件存放的戰力數值必須和資料表中的一致等等，總之不允許改資料&lt;/p&gt;
&lt;p&gt;如果先前直接開放存取，那麼這下子要改的地方就變成「所有使用到這個屬性的地方」，再要是當場看到 Visual Studio 上面寫：&lt;code&gt;99 個參考&lt;/code&gt;，那可能當場整個腦子就直接下班了。&lt;/p&gt;
&lt;p&gt;但如果我們是使用 &lt;code&gt;get&lt;/code&gt; 和 &lt;code&gt;set&lt;/code&gt; 的方式去處理的話，那麼我們只需要修改 &lt;code&gt;get&lt;/code&gt; 存取子的規則，讓它讀取的時候幫忙四捨五入到小數點第二位就好了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; _power;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Power 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._power = &lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt;; } 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Math.Round(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._power, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣應該就能看出使用 &lt;code&gt;get&lt;/code&gt; 和 &lt;code&gt;set&lt;/code&gt; 去把攔位封裝起來的好處了，也就是：&lt;strong&gt;把「資料進出時加以處理的主導權」留在物件本身&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而在 C# 裡，如果你並沒有（或是說「還沒有」）要特別針對存取另做額外處理，可以直接使用&lt;strong&gt;自動實作&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣實際上就會自動幫你建立一個私有屬性，並且只能經由這個公開屬性的 &lt;code&gt;set&lt;/code&gt; 和 &lt;code&gt;get&lt;/code&gt; 進行存取。&lt;/p&gt;
&lt;p&gt;藉由自動實作來簡化寫法之後，例如唯讀就可以這樣寫：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或是&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用自動實作時，若要加上預設值的話請這樣寫&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; } = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;這是一張卡牌。&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以說是方便很多。將來如果要針對設值給值的地方進行修改，也會比較方便一點。&lt;/p&gt;
&lt;p&gt;稍微了解了上面提到的存取範圍、存取子、自動實作這些工具之後，現在，我們就可以決定外部的使用者能看到物件的哪些部份了。&lt;/p&gt;
&lt;h2 id=&#34;隱藏複雜度&#34;&gt;隱藏複雜度&lt;/h2&gt;
&lt;p&gt;當然，封裝的概念並不僅僅只是對屬性定義存取範圍如此而已，&lt;strong&gt;提高類別內的內聚性，降低對外的耦合性，隱藏複雜資訊才是最重要的方針&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是說，我們需要妥善地運用「把大的類別和方法切割成小的類別和方法」、「活用存取範圍，對外隱藏複雜資訊、對內切割成各個工作的私有方法」等等技巧，才能夠更接近完善的封裝一點。然而這只能在設計時，或是維護到頭痛才能親自體會了。&lt;/p&gt;
&lt;p&gt;接續著上面的技巧來說：&lt;strong&gt;當你面對在一個公開方法中需要處理一長串的商業邏輯，以至於需要將他們切割成數個小函式時，將它們宣告成 &lt;code&gt;Private&lt;/code&gt; 就是相當好的選擇&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如說，我們有個連線到資料庫取得客戶資料的方法（可能是 &lt;code&gt;UserRepository.Get(int UserId)&lt;/code&gt; 這種感覺），可能我們除了 &lt;code&gt;Public&lt;/code&gt; 的 &lt;code&gt;Get&lt;/code&gt; 方法以外，還有一些 &lt;code&gt;Private&lt;/code&gt; 的 &lt;code&gt;ConnectDB&lt;/code&gt; 等輔助方法。&lt;/p&gt;
&lt;p&gt;這意味著這些工具僅讓你的物件內部使用，外面的人不應該直接調用其中的任何功能，同時又能幫助你的主要流程變得更簡潔，提升維護和修改時的速度。&lt;/p&gt;
&lt;p&gt;同時以資料庫的例子來說：&lt;strong&gt;呼叫這個函式的使用端不需要知道這個函式是怎麼連線到資料庫的，又是怎麼搜尋出資料的，只需要知道呼叫了之後能拿到客戶資料就好&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;兩個物件之間「知道」得越多，其耦合就越高。替換和修改時互相牽連的機會和規模也越大，因此封裝可以說是物件導向的基石也不為過。封裝的好或不好（亦即物件是否足夠內聚，其職責是否單一，暴露內部資訊的多寡等等），直接關係到整個架構的優劣，不可謂不慎。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：封裝的核心在於「隱藏複雜資訊」。而我們前段所提到需要注意的幾個部份：&lt;/p&gt;
&lt;p&gt;「物件是否內聚」可以參照 &lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;「職責是否單一」可以參照 &lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這些概念之間彼此相扣，此處就先按下不表。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;小結&#34;&gt;小結&lt;/h2&gt;
&lt;p&gt;封裝的部分就講到這裡，並不是很難理解，但是要封裝得好，或是說知道怎樣才算封裝得好，還是需要經驗，不是我這種菜雞一時半刻能理得白說得清的，之後有心得再和大家分享。&lt;/p&gt;
&lt;p&gt;封裝、繼承、多型並稱物件導向三大特性，我們也會按照這個順序快速地介紹。接著我們就繼續來看 &lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;繼承&lt;/a&gt; 吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;延伸閱讀：&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10203659&#34;&gt;實務上的高內聚與低耦合 - 可不可以不要寫糙 code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@ChunYeung/%E4%BB%80%E9%BA%BC%E6%98%AFoo-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%88%87%E5%B0%81%E8%A3%9D-80379c24e62&#34;&gt;什麼是OO？物件導向與封裝 - Chun Yeung - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-3_%E5%B0%81%E8%A3%9Dencapsulation%E7%B9%BC%E6%89%BFinheritance%E8%88%87%E5%A4%9A%E5%9E%8Bpolymorphism/&#34;&gt;Object Oriented物件導向-3:封裝(Encapsulation)、繼承(Inheritance)與多型(polymorphism) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/initials/2016/06/10/171117&#34;&gt;[心得整理] c# 物件導向程式 - 2.封裝、繼承、多型的三大特性 - 聊聊程式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.ithome.com.tw/node/45903&#34;&gt;思考物件導向(1)物件導向與封裝 - 蔡學鏞&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@totoroLiu/%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-object-oriented-programming-%E6%A6%82%E5%BF%B5-5f205d437fd6&#34;&gt;物件導向(Object Oriented Programming)概念 - Po-Ching Liu - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789866761799&#34;&gt;《大話設計模式》附錄：物件導向基礎&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aihuadesign.com/2020/03/16/access-modifiers-c-sharp/&#34;&gt;Public? Private? 比較各種修飾詞存取範圍 – 理工宅 Nelson&amp;rsquo;s Diary (aihuadesign.com)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/accessibility-levels&#34;&gt;存取範圍層級 - C# 參考 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/restricting-accessor-accessibility&#34;&gt;限制存取子的存取範圍 - C# 程式設計手冊 | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;推薦系列文&#34;&gt;推薦系列文&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (2): 建構式、多載</title>
      <link>https://igouist.github.io/post/2020/07/oo-2-constructor-overload/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:02 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-2-constructor-overload/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/naCjLay.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#建構式&#34;&gt;建構式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#多載&#34;&gt;多載&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#同系列文章&#34;&gt;同系列文章&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;p&gt;在上一篇&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object/&#34;&gt;類別與物件&lt;/a&gt;的文章裡，我們建立了卡片，但目前還執行不了。這是因為我們建立哥布林和戰士這兩張卡片的時候，根本就沒有給他們數值呀！&lt;/p&gt;
&lt;p&gt;雖然可以先呼叫出來再賦值…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; goblin = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;goblin.Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;哥布林&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;goblin.Attack = &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;goblin.Health = &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* ...其他賦值 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這實在相當占空間，也有點奇怪。畢竟如果是阿福（狗），一出生的時候應該就確定了一些特徵才對，例如品種、血型、眼睛顏色這種。並不會出生後過一陣子，才突然決定這些東西，既然如此，我們在產生物件的時候，當然也會希望在&lt;strong&gt;建立的同時就先決定好一部份內容&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這時候我們就可以藉由&lt;strong&gt;建構式&lt;/strong&gt;的方式，在建立物件時就進行一些我們想要的操作。&lt;/p&gt;
&lt;h2 id=&#34;建構式&#34;&gt;建構式&lt;/h2&gt;
&lt;p&gt;事實上，&lt;strong&gt;當我們呼叫 &lt;code&gt;new Card()&lt;/code&gt; 的時候&lt;/strong&gt;（不覺得這個 () 很有呼叫方法的感覺嗎？）&lt;strong&gt;我們就是正在調用 Card 的建構式&lt;/strong&gt;。而當我們沒有特別去定義建構式的時候，就會直接使用內建的建構式去幫我們產生物件。&lt;/p&gt;
&lt;p&gt;現在我們替 Card 新增一個建構式：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; attack, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; health)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Name = name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Attack = attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health = health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* ... 其他屬性和方法 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 C# 的時候，建構式必須和類別同名，且不需要定義回傳類型。當我們有了建構式，剛剛的例子就可以改寫成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; goblin = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card(name: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;哥布林&amp;#34;&lt;/span&gt;, attack: &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, health: &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; warrior = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card(name: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;戰士&amp;#34;&lt;/span&gt; , attack: &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, health: &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;warrior.Hit(goblin);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;建構式也就是建立這個物件時執行的函式&lt;/strong&gt;，通常會用來進行初始化，也就是做一些建立物件必要的準備。例如傳遞必要屬性或是建構需要的其他物件、或是給予私有屬性初始值等等，例如說我們的卡牌一建立，就會需要知道它的名字和戰鬥力，這樣才有卡牌的感覺，而不該像一些 &lt;a href=&#34;https://zh.wikipedia.org/wiki/%E6%AD%A6%E8%97%A4%E9%81%8A%E6%88%B2&#34;&gt;壞決鬥者&lt;/a&gt; 邊打牌邊偷偷印卡。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：當然有建立時執行的，也就會有消滅時執行的。請參見 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/destructors&#34;&gt;解構式&lt;/a&gt;，由於較少用到，此處先按下不表。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;多載&#34;&gt;多載&lt;/h2&gt;
&lt;p&gt;當然，有了建構式就會有更多問題。現在我們只有一個方法可以建立卡牌了，這無疑是相當不彈性的，例如說我希望預設的攻擊力和血量就是四呢？實際上我們經常會遇到需要用不同素材去建立一個物件的場合，這時候就必須得提到另一個要點：多載了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多載指的就是可以有很多個同樣名字的方法，各自去接不同的參數&lt;/strong&gt;。例如說我們的 Card 建構式就可以利用多載來改造一下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; attack, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; health)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Name = name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Attack = attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health = health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Name = name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Attack = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Level;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Level;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card ()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Noname&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Attack = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Level;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Level;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一來，我們在建立卡片的時候就能夠有更多選擇了，現在我們可以根據狀況給予需要的參數，剩下的就交給建構式去處理就好。&lt;/p&gt;
&lt;p&gt;實務上，如果規則或是建立的步驟一致的話，為了能夠把規則集中到一個地方方便修改，並且減少多餘的程式碼。我們通常會試著讓其他的建構式去呼叫主要的建構式，在 C# 中，呼叫自己的建構式是使用 &lt;code&gt;: this()&lt;/code&gt; 來進行的，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; attack, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; health)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Name = name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Attack = attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health = health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 會呼叫上面那個建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name) : &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;(name: name, attack: &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, health: &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫完 Card(name, attack, health) 之後做的事&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 會呼叫上面那一個建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card () : &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;(name: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Noname&amp;#34;&lt;/span&gt;) 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 呼叫完 Card(name) 之後做的事   &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一來只要建構的方式有變更，我們只需要集中修改第一個建構式就好了。這部份的流程也可以參照 &lt;a href=&#34;https://dotblogs.com.tw/yc421206/2011/07/25/32097&#34;&gt;[C#.NET] 為建構子建立正確的初始化 - 余小章 @ 大內殿堂&lt;/a&gt; 這篇的說明。&lt;/p&gt;
&lt;p&gt;當然多載也不只是用在建構子，而是大多數時候都可以用的寫法。例如當你的函式&lt;strong&gt;雖然做同樣的事，但允許接收不同的參數來處理時，就請考慮使用多載&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如說當你要記錄發生錯誤時的 Log ，就能允許只傳遞錯誤內容，或是傳遞錯誤內容和當時操作的參數，甚至是當下的環境資料等等。&lt;/p&gt;
&lt;p&gt;又或者是查詢客戶資料（GetUser 之類的）的函式，提供使用 客戶代號，或是 訂單編號 等不同的方式進行查詢時，就可以考慮多載的應用。&lt;/p&gt;
&lt;p&gt;例如說 .net 中協助類別對映的名套件 &lt;a href=&#34;https://igouist.github.io/post/2020/07/automapper&#34;&gt;AutoMapper&lt;/a&gt;，在轉換類別的 Map 方法就很漂亮地使用了多載：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DAgNTwc.webp&#34;
  alt=&#34;&#34;width=&#34;1000&#34; height=&#34;377&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：看不到圖片的可以直接看 &lt;a href=&#34;https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/Mapper.cs&#34;&gt;Github&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;當然隨著多載的應用越來越稀鬆平常，時至今日，我們只要使用&lt;strong&gt;選擇性參數&lt;/strong&gt;就可以輕鬆達到一樣的效果囉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Card (&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Noname&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; attack = &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; health = &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Name = name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Attack = attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health = health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當有預設值的時候，該參數就會變成可選的，這時候就可以輕鬆決定要傳進來的內容了。當然，如果傳進來的並非只是數量上的差別，而是整個型別都不一樣的話，還是要回歸到多載的做法，建立兩個同名但不同傳入參數類型的方法，可讀性會比較高呦。&lt;/p&gt;
&lt;p&gt;多載提供的好處在於：&lt;strong&gt;同個目標的函式可以根據傳入的參數不同做不一樣的處理&lt;/strong&gt;。例如當我們寫了一個連線取資料的方法，可以分為&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;傳入連線的話，就使用連線取得資料&lt;/li&gt;
&lt;li&gt;傳入連線字串的話，就先用連線字串開啟連線，再使用連線取得資料&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;等等，根據參數的場合來進行處理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;藉由傳入不同的參數類型和數量，就可以處理不同狀況的內容，既擴展了函式在使用上的彈性，同時也增加函式能派上用場的時機&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而最重要的是這將讓編寫程式的人員去思考：&lt;strong&gt;我設計的這個方法將能應用在什麼場景？&lt;/strong&gt; 這將會成為一個相當優良的習慣。&lt;/p&gt;
&lt;p&gt;那麼這次就說到這裡，我們&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;下篇&lt;/a&gt;見！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2018/02/java-overload.html&#34;&gt;Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) - 菜鳥工程師肉豬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-2_%E5%BB%BA%E6%A7%8B%E5%BC%8Fconstructor%E5%A4%9A%E8%BC%89overloading%E8%88%87%E8%A6%86%E5%AF%ABoverriding/&#34;&gt;Object Oriented物件導向-2:建構式(Constructor)、多載(Overloading)與覆寫(Overriding) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2011/07/25/32097&#34;&gt;[C#.NET] 為建構子建立正確的初始化 - 余小章 @ 大內殿堂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://adon988.logdown.com/posts/1185453-c-destructors-teaching-notes-using-visual-studio&#34;&gt;C# 解構子 Destructors - 教學筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789866761799&#34;&gt;《大話設計模式》附錄：物件導向基礎&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (1): 類別、物件</title>
      <link>https://igouist.github.io/post/2020/07/oo-1-class-object/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:01 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-1-class-object/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TIEIXm5.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;直覺上你當然知道什麼是物件；物件就在你的身邊。&lt;/p&gt;
&lt;p&gt;汽車、iPhone、收音機、吐司機、廚房用具等等，你說得出來的都是。&lt;/p&gt;
&lt;p&gt;　　——《深入淺出學會編寫程式》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;什麼是物件？一切都是物件。&lt;/p&gt;
&lt;p&gt;物件導向試圖讓抽象的程式碼，更貼近於我們的實際生活，其認為一切是由各式各樣的人事物互動所組成的，因此有了物件這個共通、最基本的概念。&lt;/p&gt;
&lt;p&gt;假設現實世界存在一頭狗，叫做阿福。而我們想要在虛擬世界裡表達「有一隻叫做阿福的狗」這件事&lt;/p&gt;
&lt;p&gt;這時候就要在系統裡有一個能代表「阿福（狗）」的東西存在，也就是「阿福」這個物件。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於「把現實世界的物件，抽象化成程式世界裡的物件」的邏輯，可以參考 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10201905&#34;&gt;一個語言如果不改變你的思考方式，就不值得學？談程式語言的本質&lt;/a&gt; 這篇，尤其是選擇保留哪些資訊的部份我認為描述得很好。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在我們知道，&lt;strong&gt;物件就是用來在虛擬世界中代表「某個特定的東西」&lt;/strong&gt;，例如說叫做阿福的狗就是一個物件，阿福今天晚上要吃的飼料罐也是一個物件。&lt;/p&gt;
&lt;p&gt;理解物件的概念是相當直覺且迅速的，畢竟你我身邊有著數不清的東西，它們都是一件一件的物件，但這樣的理解還不夠明確。&lt;/p&gt;
&lt;p&gt;就像前文所引的 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10201905&#34;&gt;談程式語言的本質&lt;/a&gt; 文中所提到的，在抽象化的同時我們必然要選擇保留哪些資訊。&lt;/p&gt;
&lt;p&gt;例如說阿福這隻狗，是一個物件；飼料罐也是一個物件&lt;/p&gt;
&lt;p&gt;而這些物件之間還會彼此互動，例如說阿福是一隻狗，而我們觀察到狗都有吃東西這個&lt;strong&gt;動作&lt;/strong&gt;，例如「阿福吃了飼料」&lt;/p&gt;
&lt;p&gt;同時物件也會有一些專屬於它的&lt;strong&gt;特徵&lt;/strong&gt;，例如說阿福是黑色的，我們就知道狗有毛色的差別。&lt;/p&gt;
&lt;p&gt;那麼我們要怎麼表達「阿福」作為一隻「狗」擁有的那些動作和特徵呢？狗的毛色？狗可以吃飼料？&lt;/p&gt;
&lt;p&gt;我們需要選擇怎麼去描述「狗」－－也就是阿福這隻狗，被我們抽象化後的樣子，我們需要將它用程式碼的方式定義出來。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這時候我們就會需要類別，來定義出我們觀察到同一類的物件該有哪些特徵和動作，也就是我們替物件「分門別類」後、篩選出特定資訊的抽象化結果&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;延續前面的例子，假設今天我們從阿福身上觀察到進食跟毛色兩個狗的重要資訊，我們就可以建立類別 Dog：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Dog&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; color;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Eat(IFood food) { &lt;span style=&#34;color:#75715e&#34;&gt;/* 進食與消化之類的 */&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;藉由我們定義的類別，就可以從類別中實例化（＝建立）出物件。&lt;/p&gt;
&lt;p&gt;也就是說，現在我們終於可以用「狗」這個類別，來表達出我們需要的「阿福」了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Dog afu = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Dog(); &lt;span style=&#34;color:#75715e&#34;&gt;// 阿福是一隻狗&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Console.Write(afu.Color); &lt;span style=&#34;color:#75715e&#34;&gt;// 阿福是黑色的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;afu.Eat(food); &lt;span style=&#34;color:#75715e&#34;&gt;// 阿福會吃食物&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;類別最常看到的比喻，就是物件的設計圖。我們藉由類別去定義我們要的物件有什麼特徵、有什麼功能，再從類別根據設計圖產生物件出來使用。&lt;/p&gt;
&lt;p&gt;這個從類別中產生的物件，就等於是這個類別定義的一個實際的例子，所以我們也會把類別產生的物件叫做&lt;strong&gt;實例&lt;/strong&gt;。而從類別產生物件的過程，就叫做實例化。&lt;/p&gt;
&lt;p&gt;也就是說，類別實際上就是我們認知中對這個物件的定義，我們篩選出&lt;strong&gt;我們需要的、我們認為這個物件應該具有的這些特徵和功能&lt;/strong&gt;，按照我們的認知去設計了類別&lt;/p&gt;
&lt;p&gt;接著我們再利用這個類別告訴程式如何建立出我們認為的這個物件，最終我們才能在程式中使用我們需要的這個物件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;當我們定義了一個狗的類別，我們實際上是在描述我們眼中的、我們歸納出來的、我們需要的「狗」&lt;/strong&gt;，我們認為狗就是會吃東西。&lt;/p&gt;
&lt;p&gt;接著，&lt;strong&gt;我們再從我們設計好的這份定義，去實例化出我們需要的狗：阿福&lt;/strong&gt;，於是阿福就有了吃東西的能力。&lt;/p&gt;
&lt;p&gt;我們有了抽象化的設計圖之後，就可以利用這個設計圖去建立多個符合這個設計的物件。例如說前面的狗，我們就可以建立出黑色的阿福，黃色的阿黃等等。&lt;/p&gt;
&lt;p&gt;在現實中，我們將阿福、阿黃等等歸類為狗這個概念，而到了程式裡，我們利用這個狗的概念定義出類別，進而產生阿福、阿黃。&lt;/p&gt;
&lt;p&gt;從這邊也能察覺到：類別是一個歸納好的概念，這概念中包含許多獨立的個體，也就是物件，而這些物件之間的差異則從我們定義類別時選擇的特徵去區分。&lt;/p&gt;
&lt;p&gt;因此，一個人設計的類別，和他使用物件的方式，反映了他對於這個物件的看法和他覺得需要的內容。&lt;/p&gt;
&lt;p&gt;同時，&lt;strong&gt;使用物件導向也意味著：比起 &lt;code&gt;EatFood(dog, food)&lt;/code&gt; 而言，我們認同 &lt;code&gt;Dog.Eat(food)&lt;/code&gt; 更直覺和易於理解。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那麼這邊就讓我們以卡牌遊戲舉例，理所當然卡牌遊戲不能沒有卡牌。&lt;/p&gt;
&lt;p&gt;對我來說卡牌通常都需要這些&lt;strong&gt;特徵&lt;/strong&gt;，我們在物件裡稱為&lt;strong&gt;屬性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;卡片名稱&lt;/li&gt;
&lt;li&gt;攻擊力&lt;/li&gt;
&lt;li&gt;防禦力&lt;/li&gt;
&lt;li&gt;卡牌描述&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;等等，另外卡牌也應該能作出某些&lt;strong&gt;動作&lt;/strong&gt;，也就是這個物件的&lt;strong&gt;方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;攻擊&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我們確認了這些要素以後，就可以把它設計成一個類別 &lt;code&gt;Card&lt;/code&gt; 如下&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Level;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Health;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 攻擊目標卡片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Hit(Card target) 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        target.damage(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Attack)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 被攻擊的時候扣血&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; damage(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; attack) 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health -= attack;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.Health &amp;lt;= &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        { 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;/* 可能呼叫死翹翹方法？ */&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;抱歉我菜，如果有真的設計卡牌遊戲的工程師經過拜託不要打我。&lt;/p&gt;
&lt;p&gt;接著我們就能在需要的時候藉由這個類別來實例化我們的卡牌：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; goblin = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; warrior = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;warrior.Hit(goblin);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;再提醒一次：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;類別是定義、是設計圖、是描述；物件是類別產生的實體、是實際上的執行者&lt;/li&gt;
&lt;li&gt;類別是抽象化的資訊，例如「狗」&lt;/li&gt;
&lt;li&gt;物件則是一個特定的實例，例如「一隻叫做阿福的狗」&lt;/li&gt;
&lt;li&gt;承上，狗的類別用來告訴程式什麼是狗；而叫做阿福的物件，則是程式根據我們的指示，從類別建立出來的一條實際的狗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這邊推薦一下可以閱讀保哥的這篇 &lt;a href=&#34;https://blog.miniasp.com/post/2009/08/27/OOP-Basis-What-is-class-and-object&#34;&gt;物件導向基礎：何謂類別(Class)？何謂物件(Object)？&lt;/a&gt;，裡面除了對物件和類別有更易懂的介紹和舉例以外，還有十題概念題可以幫助你搞懂物件和類別的意義與差異，相當值得一看。看完上面這篇之後，推薦再閱讀這幾篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ytyubox.github.io/posts/2020/02/29/oop-discussion/&#34;&gt;有物件導向的世界與沒有物件導向的世界&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2017/10/java_24.html&#34;&gt;Java 物件導向的概念 - 菜鳥工程師肉豬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-1_%E9%A1%9E%E5%88%A5class%E8%88%87%E5%AF%A6%E9%AB%94object/&#34;&gt;Object Oriented物件導向-1:類別(Class)與實體(Object) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;物件的部份由於是最初的概念，不免多廢話了一些。下一篇開始就讓我們快速看過物件導向的幾項功能和特性。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2022.12.02 補充：感謝公司前輩簡潔有力的說明，修正了開頭時對物件的介紹&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10201905&#34;&gt;一個語言如果不改變你的思考方式，就不值得學？談程式語言的本質&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2009/08/27/OOP-Basis-What-is-class-and-object&#34;&gt;物件導向基礎：何謂類別(Class)？何謂物件(Object)？ - The Will Will Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ytyubox.github.io/posts/2020/02/29/oop-discussion/&#34;&gt;有物件導向的世界與沒有物件導向的世界&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2017/10/java_24.html&#34;&gt;Java 物件導向的概念 - 菜鳥工程師肉豬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-1_%E9%A1%9E%E5%88%A5class%E8%88%87%E5%AF%A6%E9%AB%94object/&#34;&gt;Object Oriented物件導向-1:類別(Class)與實體(Object) - Sian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://notepad.yehyeh.net/Content/CSharp/CH01/03ObjectOrient/1ObjectAndClass/index.php&#34;&gt;[C#] 物件與類別 - Yehyen&amp;rsquo;s Notepad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/object-oriented-programming&#34;&gt;物件導向程式設計（C#） - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@totoroLiu/%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-object-oriented-programming-%E6%A6%82%E5%BF%B5-5f205d437fd6&#34;&gt;物件導向(Object Oriented Programming)概念 - Po-Ching Liu - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tenlong.com.tw/products/9789866761799&#34;&gt;《大話設計模式》附錄：物件導向基礎&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.books.com.tw/products/0010824582&#34;&gt;《深入淺出學會編寫程式》Ch7：模組、方法、類別以及物件&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;同系列文章&#34;&gt;同系列文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>菜雞與物件導向 (0): 前言</title>
      <link>https://igouist.github.io/post/2020/07/oo-0-object-oriented/</link>
      <pubDate>Sun, 12 Jul 2020 23:53:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/oo-0-object-oriented/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9UtcyAf.webp&#34;
  alt=&#34;&#34;width=&#34;850&#34; height=&#34;315&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在訂便當告一段落之後，其實就已經和同事約好要來整理公司新訓的筆記。但儘管已經到職快一年了，有些工具已經在專案碰過好幾次。但遇到需要跟朋友討論，或是聽前輩說明觀念的時候，還是不自主地會想「我真的懂嗎？」故一直是挺畏懼的。&lt;/p&gt;
&lt;p&gt;但幸虧同事的鼓勵和以身作則，最終還是開啟了這個新系列，決定直接開坑把當初前輩新訓指導過的部分整理下來，也算是讓自己能趁著這機會好好複習一番，把自己的想法跟心得記錄下來。&lt;/p&gt;
&lt;p&gt;另外，如果你是真心希望弄懂物件導向的朋友，這邊推薦&lt;a href=&#34;https://www.tenlong.com.tw/products/9789866761799&#34;&gt;《大話設計模式》&lt;/a&gt;的附錄，內容對物件導向的介紹清晰易懂且循序漸進，非常適合作為了解物件導向的起頭。&lt;/p&gt;
&lt;p&gt;本篇的段落將會分成以下幾個部份，由於只是筆記一下，因此會附上一些知識點的參考資料，看見的時候可以先行閱讀；末尾也會附上有關的參考資料及文章，對於這類概念性的東西，一向是推薦多方閱讀以增強理解，就像保哥寫的：「&lt;strong&gt;如果你問 100 個人這個問題，可能會得到 200 個答案，所以你一定要有自己獨到、有自信、精闢的見解或描述方式&lt;/strong&gt;。」如果有寫得不錯的文章想推薦給我，或是有地方需要補充和指證，還請不吝指教。共勉之。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-0-object-oriented&#34;&gt;菜雞與物件導向 (0): 前言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-2-constructor-overload&#34;&gt;菜雞與物件導向 (2): 建構式、多載&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-3-encapsulation&#34;&gt;菜雞與物件導向 (3): 封裝&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-4-inheritance&#34;&gt;菜雞與物件導向 (4): 繼承&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-5-polymorphism&#34;&gt;菜雞與物件導向 (5): 多型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-6-abstract-override&#34;&gt;菜雞與物件導向 (6): 抽象、覆寫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-7-interface&#34;&gt;菜雞與物件導向 (7): 介面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling&#34;&gt;菜雞與物件導向 (8): 內聚、耦合&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/09/oo-9-solid&#34;&gt;菜雞與物件導向 (9): SOLID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;菜雞與物件導向 (10): 單一職責原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-11-open-closed-principle&#34;&gt;菜雞與物件導向 (11): 開放封閉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle&#34;&gt;菜雞與物件導向 (12): 里氏替換原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/11/oo-13-interface-segregation-principle&#34;&gt;菜雞與物件導向 (13): 介面隔離原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle&#34;&gt;菜雞與物件導向 (14): 依賴反轉原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle&#34;&gt;菜雞與物件導向 (15): 最少知識原則&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/01/oo-ex1-end2020&#34;&gt;菜雞與物件導向 (Ex1): 小結&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後，儘管這系列的篇幅會有點長，但我覺得仍做不到說明透徹，&lt;del&gt;畢竟大部份也是只花一天壓線趕工出來的&lt;/del&gt;。因此往後如果有想到什麼地方必須修正，或是找到什麼更好的表達方式，還是會回來修改這系列文章，希望能逐漸修訂成讓我自己都能輕鬆看懂的物件導向入門文。&lt;/p&gt;
&lt;p&gt;下一篇就直接從類別和物件開始囉。那麼，還請各位閱覽拙見，為此先道一聲感謝。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本系列下一篇：&lt;a href=&#34;https://igouist.github.io/post/2020/07/oo-1-class-object&#34;&gt;菜雞與物件導向 (1): 類別、物件&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;8/9 註記：和朋友們聊過之後，發現之前集中成一篇感覺太長了，更何況新訓的內容也是包山包海。因此將原先的內文拆解成多篇文章，做成系列文；由於一些大人的原因（例如不想打亂鼠年全馬、不想重註冊 URL 等等），本篇就留著作為目錄使用，以上。&lt;/p&gt;&lt;/blockquote&gt;</description>
    </item>
    
    <item>
      <title>AutoMapper —— 類別轉換超省力</title>
      <link>https://igouist.github.io/post/2020/07/automapper/</link>
      <pubDate>Sun, 05 Jul 2020 23:40:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/07/automapper/</guid>
      <description>&lt;p&gt;類別間的轉換幾乎是每個專案每個工程師都會碰到的動作，舉凡是分層架構每層之間的轉換，如 Dto 轉換成 ViewModel；或是接收到資料要塞進自定義的類別時也需要進行轉換。但&lt;strong&gt;在遠古時代，當我們要把一個類別的資料倒進另一個類別時，總免不了一番折騰&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如一個卡片對戰遊戲的資料庫，光是要先把卡片資料讀取出來就需要：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/c81Hx5I.webp&#34;
  alt=&#34;&#34;width=&#34;958&#34; height=&#34;756&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;有些時候也會看見用 Foreach 然後逐一傳值的場景，或是各種差不多的變種情況。同樣的是，光是將一個簡單的卡片資訊轉換成 ViewModel，就花了一大段在做對映的處理。這個過程本身枯燥乏味又占空間，更可怕的是，&lt;strong&gt;如果有個陳年資料表，動不動就上百個欄位，那這個轉換過程的恐怖程度可想而知&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;幸好！天無絕人之路，這種時候就是本日的主角 —— AutoMapper 出場的時候了。&lt;/p&gt;
&lt;p&gt;當 AutoMapper 一出手，轉換的過程瞬間就變成：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KxHAKpi.webp&#34;
  alt=&#34;&#34;width=&#34;958&#34; height=&#34;468&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;是不是精簡很多呢？接著就讓我們來看看怎麼開始使用吧！&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝與使用&#34;&gt;安裝與使用&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#客製化轉換&#34;&gt;客製化轉換&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#忽略指定的欄位&#34;&gt;忽略指定的欄位&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#簡化整理&#34;&gt;簡化整理&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#profile&#34;&gt;Profile&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#整理到建構式&#34;&gt;整理到建構式&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#走向依賴注入&#34;&gt;走向依賴注入&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#結語&#34;&gt;結語&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料及延伸閱讀&#34;&gt;參考資料及延伸閱讀&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;安裝與使用&#34;&gt;安裝與使用&lt;/h2&gt;
&lt;p&gt;首先，當然要先到 NuGet 把 AutoMapper 給裝下來。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TEAZwU7.webp&#34;
  alt=&#34;&#34;width=&#34;534&#34; height=&#34;458&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3ADyJXv.webp&#34;
  alt=&#34;&#34;width=&#34;1017&#34; height=&#34;451&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;確認安裝之後，就開始基本的使用吧。&lt;/p&gt;
&lt;p&gt;只需要&lt;strong&gt;先註冊好 Mapper 的設定內容，也就是哪些類別之間可以互相對映；再實體化 Mapper 出來使用就可以了&lt;/strong&gt;。直接看程式碼應該就能理解：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetCard()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.GetCardFromRepositoryMock();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cfg.CreateMap&amp;lt;Card, CardViewModel&amp;gt;()); &lt;span style=&#34;color:#75715e&#34;&gt;// 註冊Model間的對映&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper(); &lt;span style=&#34;color:#75715e&#34;&gt;// 建立 Mapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = mapper.Map&amp;lt;IEnumerable&amp;lt;CardViewModel&amp;gt;&amp;gt;(data); &lt;span style=&#34;color:#75715e&#34;&gt;// 轉換型別&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;沒錯，只需要簡簡單單幾步， &lt;strong&gt;AutoMapper 會自動將指定類別間同樣名稱的屬性內容做轉換&lt;/strong&gt; ，因此能夠省下相當多的功夫，也能夠隱藏類別本身的內容，專心在處理功能本身。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：網路上挺多 AutoMapper 的文章會直接使用 &lt;code&gt;Mapper.CreateMap()&lt;/code&gt; 這類靜態方法，然而這些方法已經在 AutoMapper 5 的時候被廢除，改成現在由 &lt;code&gt;MapperConfiguration&lt;/code&gt; 產生 Mapper 的方法。&lt;/p&gt;
&lt;p&gt;然而整體的使用方式仍然大同小異，這些文章仍能稍微調整並使用，惟使用過程中需自己注意語法的差異和多加測試。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;客製化轉換&#34;&gt;客製化轉換&lt;/h3&gt;
&lt;p&gt;但當然實際上我們並不一定每個欄位的名稱都是一模一樣的，總是會有遇到同個資料在不同層的 Model 裡名稱不一樣的時候，又或者是某個欄位是由兩三個欄位組成的時候。對於這種需要客製化的轉換，這時候我們就可以在註冊對映時對欄位內容的對映方式做指定：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.CreateMap&amp;lt;Card, CardViewModel&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .ForMember(x =&amp;gt; x.Id, y =&amp;gt; y.MapFrom(o =&amp;gt; o.CardId))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .ForMember(x =&amp;gt; x.Name, y =&amp;gt; y.MapFrom(o =&amp;gt; &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{o.Id}: {o.Name}&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ); &lt;span style=&#34;color:#75715e&#34;&gt;// 註冊Model間的對映 建立設定檔&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到 &lt;strong&gt;我們能用 &lt;code&gt;ForMember&lt;/code&gt; 去對類別成員做操作，並傳入目標的類別成員，以及該成員對應的操作，像這邊就使用 &lt;code&gt;MapFrom&lt;/code&gt; 來指定目標類別成員的轉換來源&lt;/strong&gt;，這樣就可以達到轉換時的客製化了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：對於欄位之間有使用 &lt;code&gt;ForMember&lt;/code&gt; 來處理過內容再轉換的型別，建議在轉換型別時，也就是實際 mapper.Map() 的時候，稍做註解提醒一下。&lt;/p&gt;
&lt;p&gt;尤其是會將 Mapper 的 Config 集中整理的架構裡更應該要對額外的操作做揭露，這樣對彼此都好。真的。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：AutoMapper 在轉換像是 &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; 這類的集合時，預設會將來源為 Null 的成員轉換成空集合（而非 Null）&lt;/p&gt;
&lt;p&gt;如果希望 Null 就乖乖轉換成 Null 的朋友，可以在建立設定檔時用 &lt;code&gt;cfg.AllowNullCollections = true;&lt;/code&gt; 來讓亂跑的空集合轉換對象都保持 Null&lt;/p&gt;
&lt;p&gt;也能夠在 ForMember 的時候使用 AllowNull 來針對欄位指定規則。&lt;br/&gt;例如 &lt;code&gt;ForMember(x =&amp;gt; x.Members, y =&amp;gt; y.AllowNull())&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;需要更詳細說明的朋友，也可以參照官方文檔的 &lt;a href=&#34;https://docs.automapper.org/en/stable/Lists-and-arrays.html#handling-null-collections&#34;&gt;Handling null collections&lt;/a&gt; 一節&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;忽略指定的欄位&#34;&gt;忽略指定的欄位&lt;/h3&gt;
&lt;p&gt;除了會有欄位名稱和來源的不同以外，最常遇到的應該就是兩個類別之間大多數欄位雖然對映，但某幾個欄位是沒有對映的。例如傳送給前端使用時，我們想要一併組裝多個來源的資料，所以資料由資料表取出時並沒有某些欄位，必須等後續的步驟去別的來源補上資料。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這時候如果直接使用 Map 來進行轉換，就會發生找不到對映欄位的錯誤。此時就可以先用 &lt;code&gt;Ignore&lt;/code&gt; 來忽略掉指定的欄位&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.CreateMap&amp;lt;Card, CardViewModel&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       .ForMember(x =&amp;gt; x.ImgUri, y =&amp;gt; y.Ignore())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如上述程式碼，可能卡片的圖像位置必須由別的方式取得，這種情況我們在 &lt;code&gt;ForMember&lt;/code&gt; 的時候就可以將其指定為 &lt;code&gt;Ignore&lt;/code&gt; 讓 AutoMapper 不要去嘗試轉換它。&lt;/p&gt;
&lt;p&gt;除了 &lt;code&gt;Ignore&lt;/code&gt; 以外，AutoMapper 還有 &lt;code&gt;IgnoreAllPropertiesWithAnInaccessibleSetter&lt;/code&gt; 這種光方法名稱長度就有點強的大殺器，也還有像是 &lt;code&gt;AllowNull&lt;/code&gt; 等更多對欄位操作的方法，這部份請各位在使用時按照需求自行摸索。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註(1)：上述舉的例子中有需要從多個來源轉換為一個類別的場合，可以參照 &lt;a href=&#34;https://kevintsengtw.blogspot.com/2014/03/automapper.html&#34;&gt;AutoMapper 兩個物件對映到一個類別 - mrkt 的程式學習筆記&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;註(2)：上面使用到的 Ignore 方法還是有些眉角需要注意，可以參照 &lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/07/17/162815&#34;&gt;[料理佳餚] AutoMapper 中不容忽視的 Ignore() Mapping 的順序 - 軟體主廚的程式料理廚房&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;註(3)：面對一些比較複雜的轉換時，也可以考慮使用 &lt;a href=&#34;https://igouist.github.io/post/2021/12/automapper-convert-using/&#34;&gt;ConvertUsing&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;最後還要補充的一個方法是 &lt;code&gt;ReverseMap&lt;/code&gt; ，它能夠反轉對映，建立一個反方向的對映表。當你的類別之間可能需要往回轉型，又或是想要忽略來源類別的欄位時，&lt;code&gt;ReverseMap&lt;/code&gt; 就會顯得相當有用。通常為了避免轉來轉去轉出意外，建議註冊時還是補上一個 &lt;code&gt;ReverseMap&lt;/code&gt; 為佳。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.CreateMap&amp;lt;Card, CardViewModel&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       .ForMember(x =&amp;gt; x.Id, y =&amp;gt; y.MapFrom(o =&amp;gt; o.CardId))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       .ForMember(x =&amp;gt; x.ImgUri, y =&amp;gt; y.Ignore())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       .ReverseMap() &lt;span style=&#34;color:#75715e&#34;&gt;// 反轉&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到目前為止，程式碼可能會變成如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetCard()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.GetCardFromRepositoryMock();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cfg.CreateMap&amp;lt;Card, CardViewModel&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           .ForMember(x =&amp;gt; x.Id, y =&amp;gt; y.MapFrom(o =&amp;gt; o.CardId))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           .ForMember(x =&amp;gt; x.Name, y =&amp;gt; y.MapFrom(o =&amp;gt; &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{o.Id}: {o.Name}&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           .ForMember(x =&amp;gt; x.ImgUri, y =&amp;gt; y.Ignore())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           .ReverseMap());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper(); &lt;span style=&#34;color:#75715e&#34;&gt;// 建立 Mapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = mapper.Map&amp;lt;IEnumerable&amp;lt;CardViewModel&amp;gt;&amp;gt;(data); &lt;span style=&#34;color:#75715e&#34;&gt;// 轉換型別&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;2021.06.09 補充：&lt;/p&gt;
&lt;p&gt;在我們前面提到的欄位轉換時，要特別注意兩個欄位的型別是否能夠對應上。由於 AutoMapper 會貼心地幫我們進行轉換，但有些時候可能會產生問題，讓我們看看下面這個例子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Boo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Foo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; Val { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Foo Sut()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; boo = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Boo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Val = &lt;span style=&#34;color:#ae81ff&#34;&gt;2.65&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cfg.CreateMap&amp;lt;Boo, Foo&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; foo = mapper.Map&amp;lt;Foo&amp;gt;(boo);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; foo; &lt;span style=&#34;color:#75715e&#34;&gt;// 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到上面這個例子中，我們可以把 &lt;code&gt;double&lt;/code&gt; 的欄位直接 Mapping 到 &lt;code&gt;int&lt;/code&gt; 的欄位上，因為 AutoMapper 的貼心，所以不會出錯，但過程中還是可能發生喪失精度的問題。&lt;/p&gt;
&lt;p&gt;像是例子中的 2.65 就直接變成了 3，如果符合我們的需求（例如本來就打算轉型）倒是沒關係，但如果是因為不小心轉到的話，在找問題上可能就會比較麻煩。&lt;/p&gt;
&lt;p&gt;因此在轉換的時候，還請特別留意一下轉換雙方的型別，測試一下轉換結果是不是自己要的結果會比較好呦。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;簡化整理&#34;&gt;簡化整理&lt;/h2&gt;
&lt;p&gt;可能看到這裡有些讀者就開始疑惑了：這好像跟一開始簡介不太一樣啊，詐欺？又或者是：我每個方法裡面都要重新建一個 Mapper？這不太對吧？&lt;/p&gt;
&lt;p&gt;的確是這樣沒錯，AutoMapper 要達到更簡潔，還需要再做一些整理。例如使用 Profile 將對映關係集中起來，以及把 Mapper 的建構抽取出來。可能是在建構式建立共用的 Mapper ，或是以依賴注入 (Dependency Injection) 的方式來注入 Mapper 等等。以下就逐步進行介紹。&lt;/p&gt;
&lt;h3 id=&#34;profile&#34;&gt;Profile&lt;/h3&gt;
&lt;p&gt;首先我們先建立一個用來放對映關係的 Profile。我個人習慣會另開一個 Mappings 資料夾，並按照轉換所在的分層 + Mappings 來命名。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ServiceMappings&lt;/span&gt; : Profile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; ServiceMappings()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CreateMap&amp;lt;Card, CardViewModel&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .ForMember(x =&amp;gt; x.Id, y =&amp;gt; y.MapFrom(o =&amp;gt; o.CardId))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .ForMember(x =&amp;gt; x.Name, y =&amp;gt; y.MapFrom(o =&amp;gt; &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{o.Id}: {o.Name}&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .ForMember(x =&amp;gt; x.ImgUri, y =&amp;gt; y.AllowNull())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .ReverseMap(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ...其他的對映內容 (使用 CreateMap&amp;lt;&amp;gt; 建立下一組)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;從上面可以看到，我們&lt;strong&gt;繼承 AutoMapper 提供的 Profile 類，接著在建構式裡面將需要的轉換關係組都建立起來&lt;/strong&gt;。接著當我們建立 Mapper 時，就可以直接用 &lt;code&gt;AddProfile&lt;/code&gt; 和建立好的 Profile 來直接讀入對映關係：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetCard()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.GetCardFromRepositoryMock();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; cfg.AddProfile&amp;lt;ServiceMappings&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapper = config.CreateMapper(); &lt;span style=&#34;color:#75715e&#34;&gt;// 用設定檔建立 Mapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = mapper.Map&amp;lt;IEnumerable&amp;lt;CardViewModel&amp;gt;&amp;gt;(data); &lt;span style=&#34;color:#75715e&#34;&gt;// 轉換型別&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當然，&lt;strong&gt;在 &lt;code&gt;MapperConfiguration&lt;/code&gt; 的時候是可以對 cfg 去做多次 &lt;code&gt;AddProfile&lt;/code&gt; 的&lt;/strong&gt;，既然可以載入多組對映關係，就代表可以按照實務上的運用去將對映關係做分類和整理。&lt;/p&gt;
&lt;p&gt;這邊還是要再度提醒一下，將 &lt;code&gt;CreateMap&lt;/code&gt; 都整理到 Profile 之後，若是有些類別之間的轉換有對欄位做額外的處理，例如 DateTime 去除時間只留下年月日，又或是某幾個欄位銜接成一個欄位等等，&lt;strong&gt;在實際進行類別轉換的時候需要註記一下，請後續的維護人員或團隊夥伴記得先確認過 Profile，避免造成一些隱藏重要資訊挖洞給人跳的問題&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;整理到建構式&#34;&gt;整理到建構式&lt;/h3&gt;
&lt;p&gt;接著由於一個處理資料或商業邏輯的部分通常會有許多方法都用到 Mapper，因此將建立 Mapper 的過程取出來避免重複絕對是必要的。故整理的第一步就是將 Mapper 的建立拆分出來，不再等每次用到的時候才建，而是先建立好一個 Mapper，需要的時候再來使用它。&lt;/p&gt;
&lt;p&gt;這邊就先嘗試將其挪到私有變數和建構式處理：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; IMapper _mapper;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; MapSimpleService() &lt;span style=&#34;color:#75715e&#34;&gt;// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; config = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapperConfiguration(cfg =&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cfg.AddProfile&amp;lt;ServiceMappings&amp;gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper = config.CreateMapper();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那麼我們實際上的方法就會變成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetCard()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.GetCardFromRepositoryMock();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;IEnumerable&amp;lt;CardViewModel&amp;gt;&amp;gt;(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;走向依賴注入&#34;&gt;走向依賴注入&lt;/h3&gt;
&lt;p&gt;由於本篇篇幅無法說明好依賴注入（DI）的概念，相關的部份就留待在&lt;a href=&#34;https://igouist.github.io/post/2021/11/newbie-6-dependency-injection&#34;&gt;之後的文章&lt;/a&gt;中說明，這邊就以 .net Core 為示範來記錄一下步驟，提供給已經會 DI 的朋友參考。&lt;/p&gt;
&lt;p&gt;在開始之前，我們會需要再前往 NuGet 安裝 AutoMapper DI 用的套件：



&lt;img
  src=&#34;https://image.igouist.net/hae1zfj.webp&#34;
  alt=&#34;&#34;width=&#34;781&#34; height=&#34;238&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著在 &lt;code&gt;Startup.cs&lt;/code&gt; 裡的 &lt;code&gt;ConfigureServices&lt;/code&gt; 加上 &lt;code&gt;services.AddAutoMapper(typeof(Startup))&lt;/code&gt; 來註冊我們的 Mapper：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services.AddControllers(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Demo 用的 Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services.AddScoped&amp;lt;IMapSimpleService, MapSimpleService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 註冊 Mapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services.AddAutoMapper(&lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt;(Startup)); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可能有些人會有點疑惑：前面還需要 &lt;code&gt;MapperConfiguration&lt;/code&gt; 怎麼這次不用了呢？那是因為 &lt;code&gt;AddAutoMapper&lt;/code&gt; 的時候就會用反射去取得同組件中的 &lt;code&gt;Profile&lt;/code&gt; 來載入，所以這部分就可以不用擔心，只要專心在使用上就好。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2021.04.23 補充：&lt;/p&gt;
&lt;p&gt;感謝同事們的討論，這邊也補充給大家參考一下&lt;/p&gt;
&lt;p&gt;當我們直接使用 &lt;code&gt;services.AddAutoMapper(typeof(Startup));&lt;/code&gt; 註冊的時候，AutoMapper 會去抓我們 &lt;code&gt;typeof&lt;/code&gt; 的型別，並對該型別所在的 Assembly 進行反射找出所有的 &lt;code&gt;Profile&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但如果你有用到多層的架構、或是不同的組件內都有 &lt;code&gt;Profile&lt;/code&gt; 需要註冊，又或者只是想要逐個 &lt;code&gt;Profile&lt;/code&gt; 進行註冊，方便進行控管的話，可以考慮使用 &lt;strong&gt;&lt;code&gt;AddProfile&lt;/code&gt;&lt;/strong&gt; 或 &lt;strong&gt;&lt;code&gt;AddProfiles&lt;/code&gt;&lt;/strong&gt; 的方式來進行註冊，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddAutoMapper(config =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    config.AddProfile&amp;lt;ControllerProfile&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    config.AddProfile&amp;lt;ServiceProfile&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣的可讀性更好，也能讓維護的人迅速掌握當前的 Profile，有需要的朋友可以嘗試看看。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2021.08.27 補充：&lt;/p&gt;
&lt;p&gt;感謝 &lt;a href=&#34;https://raychiutw.github.io/&#34;&gt;Ray&lt;/a&gt; 大大提供的方法，這邊也補充給大家參考一下&lt;/p&gt;
&lt;p&gt;我們前面有提到 &lt;code&gt;AddAutoMapper&lt;/code&gt; 會對該型別所在的 Assembly 進行反射找出所有的 &lt;code&gt;Profile&lt;/code&gt;，那麼我們也可以轉換一下思路：只要把全部的組件都丟到 &lt;code&gt;AddAutoMapper&lt;/code&gt; 裡就好了！這種時候我們就可以利用反射來達到我們的目的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;相較於 &lt;code&gt;AddProfile&lt;/code&gt; 這種逐一新增 &lt;code&gt;Profile&lt;/code&gt; 的作法，利用反射會讓註冊顯得更乾淨，並且也更不容易有所遺漏。各位可以按照開發的場景選擇一下註冊的方式，有需要的朋友也可以都嘗試看看。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;註冊好之後，我們就能將剛剛的示範部分更改為使用注入的方式來取得 Mapper：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; IMapper _mapper;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; MapSimpleService(IMapper mapper) &lt;span style=&#34;color:#75715e&#34;&gt;// 建構式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper = mapper;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;執行之後就能確認確實有轉換到值囉！最後再來對照一下商業邏輯的部份：&lt;/p&gt;
&lt;p&gt;－－瘦身前－－&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetCard()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.GetCardFromRepositoryMock();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = data.Select(x =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; CardViewModel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Id = x.Id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Name = x.Name,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Cost = x.Cost,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Attack = x.Attack,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Health = x.Health,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Note = x.Note &lt;span style=&#34;color:#75715e&#34;&gt;// ...etc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;－－瘦身後－－&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IEnumerable&amp;lt;CardViewModel&amp;gt; GetCard()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.GetCardFromRepositoryMock();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;._mapper.Map&amp;lt;IEnumerable&amp;lt;CardViewModel&amp;gt;&amp;gt;(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;結語&#34;&gt;結語&lt;/h2&gt;
&lt;p&gt;當我們把類別轉換的區段利用 AutoMapper 擷取出來，就能&lt;strong&gt;讓原本的程式碼更著重在該方法本身的邏輯和功能&lt;/strong&gt;，而不會被冗長的轉換過程洗版，使用 Mapper 的方法只要知道這兩個類別能夠互相轉換即可。除了看起來更舒服、更能將焦點放在方法本身以外，也能夠讓維護時的修改更加直覺，若是轉換的過程需要修改，就去變更 Profile，不需要去變動實現商業邏輯的方法本身，這樣就能朝單一職責的目標更前進一點。&lt;/p&gt;
&lt;p&gt;到這邊算是把 AutoMapper 基本的用法介紹過一遍了，當然還有很多進階的用法和需要注意的部分並沒有說明完全，但相信這樣的紀錄已經能夠幫助我在將來需要的時候回想起怎麼使用，和解決大部分需要做類別轉換的場景了。剩餘的延伸閱讀將會補充在參考資料裡，那麼下週見～&lt;/p&gt;
&lt;h2 id=&#34;參考資料及延伸閱讀&#34;&gt;參考資料及延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2016/07/15/automapper_version_5_MapperConfiguration&#34;&gt;[AutoMapper] AutoMapper 5.0.2 的新寫法 - 余小章 @ 大內殿堂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2013/04/automapper.html&#34;&gt;使用 AutoMapper 處理類別之間的對映轉換 - mrkt 的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2014/03/automapper.html&#34;&gt;AutoMapper 兩個物件對映到一個類別 - mrkt 的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2013/04/automapper-configuration.html&#34;&gt;AutoMapper 的設定 (Configuration) - mrkt 的程式學習筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/yc421206/2014/12/12/147619&#34;&gt;[C#.NET] 使用 AutoMapper.Profile 簡化對應設定 - 余小章 @ 大內殿堂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10157130&#34;&gt;AutoMapper 介紹 - 簡單化Entity和ViewModel之間的轉換&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/keigen/2017/06/28/151917&#34;&gt;AutoMapper 初體驗 - 中年大叔的鹹魚翻身作戰計畫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://iter01.com/155318.html&#34;&gt;.NET Core 中依賴注入 AutoMapper 小記 - IT人&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/supershowwei/2016/07/17/162815&#34;&gt;[料理佳餚] AutoMapper 中不容忽視的 Ignore() Mapping 的順序 - 軟體主廚的程式料理廚房&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/12/automapper-convert-using/&#34;&gt;AutoMapper 使用 ConvertUsing 自定義類型轉換，將包含串列成員的物件映射為一組串列&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Electron.net —— 把網頁包成桌面應用吧</title>
      <link>https://igouist.github.io/post/2020/06/electron-net/</link>
      <pubDate>Sun, 28 Jun 2020 19:41:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/06/electron-net/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://raw.githubusercontent.com/ElectronNET/Electron.NET/refs/heads/main/assets/images/electron.net-logo.png&#34;
  alt=&#34;&#34;width=&#34;1432&#34; height=&#34;436&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;因緣際會下想要弄出一些單機小工具來跑，這時候正巧接觸到 &lt;a href=&#34;https://github.com/ElectronNET/Electron.NET&#34;&gt;Electron.net&lt;/a&gt; 這個神器，特別紀錄一下以免忘記。這是 &lt;a href=&#34;https://www.electronjs.org/&#34;&gt;Electron&lt;/a&gt; 搭配 .net Core 的框架， &lt;strong&gt;Electron 是用 Chromium 和 Node.js 將網頁封裝成桌面應用程式&lt;/strong&gt;，像是 Visual Studio Code、Slack 也都有使用到 Electron。而 &lt;a href=&#34;https://github.com/ElectronNET/Electron.NET&#34;&gt;Electron.net&lt;/a&gt; 顧名思義就是 .net 用的 Electron 框架囉。&lt;/p&gt;
&lt;p&gt;這邊記錄一下自己嘗試時載入套件和建置的流程，主要參考自黑大的 &lt;a href=&#34;https://blog.darkthread.net/blog/electron-net/&#34;&gt;用 ASP.NET Core 寫桌面 GUI 應用程式 - Electron.NET&lt;/a&gt; 和 &lt;a href=&#34;https://blog.darkthread.net/blog/electron-api-brief/&#34;&gt;Electron.NET API 快速巡覽&lt;/a&gt; 這兩篇文章，以及官方的 &lt;a href=&#34;https://github.com/ElectronNET/electron.net-api-demos&#34;&gt;API DEMO&lt;/a&gt;，特此感謝。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#安裝套件及環境設置&#34;&gt;安裝套件及環境設置&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#前後端傳值-ipcmain-ipcrenderer&#34;&gt;前後端傳值 (IpcMain, IpcRenderer)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#打包成執行檔exe&#34;&gt;打包成執行檔（.exe）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#後記&#34;&gt;後記&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;p&gt;那麼首先從建立新專案開始，這邊用 .net Core MVC 來測試&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/xf2fOwT.webp&#34;
  alt=&#34;&#34;width=&#34;585&#34; height=&#34;440&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/O0jwPZF.webp&#34;
  alt=&#34;&#34;width=&#34;978&#34; height=&#34;641&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;安裝套件及環境設置&#34;&gt;安裝套件及環境設置&lt;/h2&gt;
&lt;p&gt;建立專案之後，前往 Nuget 先把 &lt;strong&gt;ElectronNET API&lt;/strong&gt; 安裝下來&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hT5JZu0.webp&#34;
  alt=&#34;&#34;width=&#34;930&#34; height=&#34;432&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝完畢之後，開始做一些前置動作，首先：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Program.cs&lt;/code&gt; 的部分，加上 &lt;code&gt;.UseElectron(args)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Vm7P1ds.webp&#34;
  alt=&#34;&#34;width=&#34;822&#34; height=&#34;545&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Startup.cs&lt;/code&gt; 的部分，加上 &lt;code&gt;Task.Run(async () =&amp;gt; await Electron.WindowManager.CreateWindowAsync()&lt;/code&gt;，讓專案啟動時一併啟動 Electron&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gvsXNx4.webp&#34;
  alt=&#34;&#34;width=&#34;920&#34; height=&#34;673&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著打開命令提示字元或 Powershell ，輸入 &lt;code&gt;dotnet tool install ElectronNET.CLI -g&lt;/code&gt; 安裝 ElectronNET 工具。如果有安裝成功應該會看到下圖的回傳。如果已經安裝，就可以直接進到下一步。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hf4mg6u.webp&#34;
  alt=&#34;&#34;width=&#34;477&#34; height=&#34;52&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著移動到專案資料夾，並輸入 &lt;code&gt;electronize init&lt;/code&gt; 進行初始化，將會建立資料夾和必需檔案，這一步一定要確認成功並顯示 &lt;code&gt;Everything done&lt;/code&gt;，否則後面怎麼跑也起不來。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/q6RdjDm.webp&#34;
  alt=&#34;&#34;width=&#34;940&#34; height=&#34;418&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;補充：如果 &lt;code&gt;electronize init&lt;/code&gt; 的時候跳出 &amp;ldquo;Path cannot be null on init&amp;rdquo; 的錯誤，請移動到 &lt;code&gt;Startup.cs&lt;/code&gt; 或是 &lt;code&gt;Program.cs&lt;/code&gt; 所在的目錄再試一次。&lt;/p&gt;
&lt;p&gt;參考來源：&lt;a href=&#34;https://github.com/ElectronNET/Electron.NET/issues/245&#34;&gt;Electron.NET&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;最後輸入 &lt;code&gt;electronize start&lt;/code&gt; 就可以準備看專案 On 起來囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HoSdpvU.webp&#34;
  alt=&#34;&#34;width=&#34;991&#34; height=&#34;452&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;另外除了 &lt;code&gt;electronize start&lt;/code&gt;，環境設置完畢後也可以直接從 Visual Studio 偵錯囉～&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/iFqkq6d.webp&#34;
  alt=&#34;&#34;width=&#34;141&#34; height=&#34;39&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;成功從桌面應用開起來了！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Y89i3HK.webp&#34;
  alt=&#34;&#34;width=&#34;807&#34; height=&#34;607&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;前後端傳值-ipcmain-ipcrenderer&#34;&gt;前後端傳值 (IpcMain, IpcRenderer)&lt;/h2&gt;
&lt;p&gt;在 Electron 中，會分成&lt;strong&gt;跑應用程式的主處理序 (main process)&lt;/strong&gt;、和&lt;strong&gt;處理網頁畫面的渲染處理序 (renderer process)&lt;/strong&gt;。而兩者之間的&lt;strong&gt;溝通則經由 &lt;code&gt;Ipc&lt;/code&gt; 開啟頻道傳遞訊息來實現&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這邊簡單嘗試一次，首先，我們先在 &lt;code&gt;Views/Home/index.cshtml&lt;/code&gt; 加上一個簡單的按鈕&lt;/p&gt;
&lt;p&gt;並且加上 JavaScript 發送訊息和接收訊息的方法&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DaNlQgS.webp&#34;
  alt=&#34;&#34;width=&#34;830&#34; height=&#34;533&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;ipcRenderer&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;require&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;electron&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 發送訊息，這邊傳送一個 Hello
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sendMessageToServer&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ipcRenderer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;send&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;channelToServer&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Hello&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 接收訊息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ipcRenderer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;on&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;channelToClient&amp;#39;&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;arg&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    document.&lt;span style=&#34;color:#a6e22e&#34;&gt;getElementById&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Welcome&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;innerText&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著到對應的 &lt;code&gt;Controllers/HomeController&lt;/code&gt; 的 &lt;code&gt;Index()&lt;/code&gt; 也加上接受到訊息之後加工並發出訊息的處理&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Xw3AX8r.webp&#34;
  alt=&#34;&#34;width=&#34;972&#34; height=&#34;633&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; IActionResult Index()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 接收訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Electron.IpcMain.On(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;channelToServer&amp;#34;&lt;/span&gt;, (args) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 傳送訊息，把接收到的訊息再加上 World&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mainWindow = Electron.WindowManager.BrowserWindows.First(); &lt;span style=&#34;color:#75715e&#34;&gt;// 現在只有一個視窗所以直接拿&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Electron.IpcMain.Send(mainWindow, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;channelToClient&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;{args}, world!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; View();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就讓我們執行看看，當我們按下按鈕&amp;hellip;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qdkrpQ5.webp&#34;
  alt=&#34;&#34;width=&#34;799&#34; height=&#34;599&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HYo0DUp.webp&#34;
  alt=&#34;&#34;width=&#34;803&#34; height=&#34;598&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就能成功在前後端之間傳值了！但要注意，它是建立一個 Channel 來傳遞訊息，所以&lt;strong&gt;兩邊的 channel 名稱可不能錯了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;另外也有提供同步版本的 &lt;code&gt;ipcRendered.sendSync()&lt;/code&gt; 與 &lt;code&gt;Electron.IpcMain.OnSync()&lt;/code&gt; 等等方法，相關的操作可以參照官方的 &lt;a href=&#34;https://github.com/ElectronNET/electron.net-api-demos&#34;&gt;API DEMO&lt;/a&gt; 中的 &lt;code&gt;Controllers/IpcController.cs&lt;/code&gt; 及 &lt;code&gt;Views/Ipc/Index.cshtml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;而關於 IPC 的說明，可以參考這篇 &lt;a href=&#34;https://medium.com/@terracotta_ko/electron-ipc-%E6%A9%9F%E5%88%B6-2a1b087c9ae5&#34;&gt;[Electron] IPC 機制&lt;/a&gt;，以及官方文件：&lt;a href=&#34;https://www.electronjs.org/docs/api/ipc-main&#34;&gt;ipcMain&lt;/a&gt;、&lt;a href=&#34;https://www.electronjs.org/docs/api/ipc-renderer&#34;&gt;ipcRenderer&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;打包成執行檔exe&#34;&gt;打包成執行檔（.exe）&lt;/h2&gt;
&lt;p&gt;既然是桌面軟體，當然是要包裝成 exe 直接執行囉。畢竟總不能做了個小工具分享給朋友，還要「欸你自己開專案建置一下」吧 XD&lt;/p&gt;
&lt;p&gt;在專案資料夾下用命令列輸入指令 &lt;code&gt;electronize build /target win&lt;/code&gt; ，其中 &lt;code&gt;/target&lt;/code&gt; 除了 win 也可以指定 osx, liunx 等等&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4iSSuCC.webp&#34;
  alt=&#34;&#34;width=&#34;882&#34; height=&#34;533&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;編譯需要一段時間，直到看見 &lt;code&gt;done&lt;/code&gt; 就可以了&lt;/p&gt;
&lt;p&gt;編出來的安裝包和執行檔會在 &lt;code&gt;bin\Desktop&lt;/code&gt; 和 &lt;code&gt;bin\Desktop\win-unpacked&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JKqNsVl.webp&#34;
  alt=&#34;&#34;width=&#34;775&#34; height=&#34;523&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;稍微打開看看&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Ywg2CoQ.webp&#34;
  alt=&#34;&#34;width=&#34;981&#34; height=&#34;621&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以正常執行！&lt;/p&gt;
&lt;h2 id=&#34;後記&#34;&gt;後記&lt;/h2&gt;
&lt;p&gt;目前只做到專案成功跑起來和一些簡單的測試而已，幸虧黑大的文章跟網路上的一些教學相當完整，並沒有花費太多時間。但是畢竟約好了要玩玩看這個框架，且許多功能沒有試過，例如 &lt;a href=&#34;https://blog.darkthread.net/blog/electron-api-brief/&#34;&gt;Electron.NET API 快速巡覽&lt;/a&gt; 提到的大多功能，&lt;del&gt;因此還在煩惱後續的紀錄要集中在這一篇還是另外開成系列文，這部分等下週更新再說囉。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;6/29 更新: 最後還是決定把最近嘗試的都集中在這一篇了，畢竟我還是挺懶的，這樣比較好找嘛～&lt;/p&gt;
&lt;p&gt;另外有興趣自己試試的朋友，也可以參閱 &lt;a href=&#34;https://www.electronjs.org/docs&#34;&gt;官方文檔&lt;/a&gt; 其實相當完整。基本上只要會寫網頁，就能夠寫桌面 GUI，真是有夠賺。&lt;/p&gt;
&lt;p&gt;最後要特別感謝一下，當我開 Visual Studio 預設的範本時，赫然發現只有 Angular 和 React 的模板可以直接用，竟然沒有 Vue！真是嚇傻我了，幸好最後有找到 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=alexandredotnet.netcorevuejs&amp;amp;ssr=false#overview&#34;&gt;VueJS with Asp.Net Core 3.1 Web API Template&lt;/a&gt; 才不致落得自造輪子的下場，感謝前輩們和社群的貢獻，南無南無。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/electron-net/&#34;&gt;用 ASP.NET Core 寫桌面 GUI 應用程式 - Electron.NET - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/electron-api-brief/&#34;&gt;Electron.NET API 快速巡覽 - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10200819&#34;&gt;ElectronNET : .NET Core+NodeJS做跨平台桌面程式 - iT邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@terracotta_ko/electron-ipc-%E6%A9%9F%E5%88%B6-2a1b087c9ae5&#34;&gt;[Electron] IPC 機制&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.electronjs.org/docs&#34;&gt;Electronjs - Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>讀《黑馬思維》</title>
      <link>https://igouist.github.io/post/2020/06/darkhorse/</link>
      <pubDate>Sat, 20 Jun 2020 00:22:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/06/darkhorse/</guid>
      <description>&lt;p&gt;


&lt;img
  src=&#34;https://www.books.com.tw/img/001/081/94/0010819494_b_01.jpg&#34;
  alt=&#34;&#34;width=&#34;1400&#34; height=&#34;1968&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每個人都是天才。但如果你用爬樹能力來斷定一條魚有多少才幹，牠整個人生都會相信自己愚蠢不堪。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;本篇整理一下這陣子讀&lt;a href=&#34;https://www.books.com.tw/products/0010819494&#34;&gt;《黑馬思維》&lt;/a&gt;這本書的筆記，以及一些個人心得。&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#知道你的微動力&#34;&gt;知道你的微動力&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#清楚你的選擇&#34;&gt;清楚你的選擇&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#了解你的策略&#34;&gt;了解你的策略&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#忽略你的目的地&#34;&gt;忽略你的目的地&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#黑馬或是標準化&#34;&gt;黑馬或是標準化&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#心得&#34;&gt;心得&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#延伸閱讀&#34;&gt;延伸閱讀&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;本書的目的是研究那些橫空出世的&lt;strong&gt;黑馬&lt;/strong&gt;。但在研究少數的黑馬之前，就必須先說明何謂多數，所以本書前段著重在介紹什麼是&lt;strong&gt;標準化&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;從工業革命至今，為了能大量生產、品質穩定、降低成本、最終達到「&lt;strong&gt;一致、大量、簡單、有效&lt;/strong&gt;」的效果，最直接且有效的做法就是制定流程和規定。不管是製造業，或者是教育，甚至是人生，群眾總是試圖找出一個固定的流程，鋪設一條筆直的道路，並且告訴大家：&lt;strong&gt;只要遵循這個路線，就能夠達到成功&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;雖然比起更加以前的階級制而言，標準化的做法的確更加公平了。然而為了達到標準一致，勢必得要重視群體、忽視個人。標準化的做法將所有人一視同仁，如同工廠的機器，或是程式的流程，&lt;strong&gt;認為設定好的輸入，經過了制定好的流程，就該有期待的產出&lt;/strong&gt;。為了達到標準，因此無法認為每個人是特別的，甚至那些過於特別的，反而對標準化而言是個麻煩。&lt;/p&gt;
&lt;p&gt;標準化的成功很直接：跟別人做一樣的事，但做得更好，就是最佳路線。&lt;/p&gt;
&lt;p&gt;但有一些人，並不遵循標準化建議的路線，仍然取得了成功，他們就是黑馬。在個人化崛起的這個時代，媒體、醫療、廣告，甚至教育都開始有了量身打造的選項，隨處可見精準投放，個人菜單，標準化漸漸被個人化取代，於是作者們提出了疑問：什麼是「&lt;strong&gt;個人化的成功&lt;/strong&gt;」呢？他們開始研究這些黑馬。&lt;/p&gt;
&lt;p&gt;直接結論：黑馬們的個性、背景和領域都不大一樣，然而他們有一部分是相似的。有些人說：我認為自己做的事情是值得的，有些人說他感覺到熱忱，也有人說這是他的天職。&lt;strong&gt;黑馬的共通點在於：他們都並非為了想要成功或是達到目標才努力前進，而是靠著追求自我實現而成就卓越&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Chase Excellence, Success will follow. - 3 Idiots&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;而對於這些黑馬如何追求自我實現，如何走出和標準化不同的路。本書提出以下四個重點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;知道你的微動力&lt;/li&gt;
&lt;li&gt;清楚你的選擇&lt;/li&gt;
&lt;li&gt;了解你的策略&lt;/li&gt;
&lt;li&gt;忽略你的目的地&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;知道你的微動力&#34;&gt;知道你的微動力&lt;/h2&gt;
&lt;p&gt;說起來很直接：&lt;strong&gt;嘗試去弄清楚：什麼對你最重要？你渴望什麼？不渴望什麼？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;標準化提升動力的方式著重在整體，例如辦公室喜歡更換座位，班級有共同獎章等等，然而那並不等於個人的動力；標準化也常提出大方向的動力，例如成長。誰不想要成長呢？但並不是對所有人有效，只一昧喊著要成長要成長，既虛幻又遙遠。我們需要的是確實一點的動力。&lt;/p&gt;
&lt;p&gt;因此，觀察自己對什麼有動力是相當重要的。書中舉了相當多例子，諸如喜歡整理實際空間，喜歡立體文物等等。微動力也可以分得很細，賞鳥就能分成喜歡看的和喜歡聽的。去觀察、找出自已的微動力，並思考如何應用它，讓生計結合天性，才能更有動力，也更加快樂。&lt;/p&gt;
&lt;p&gt;但要觀察自己的動力是不容易的，因此書中提出了&lt;strong&gt;評判遊戲&lt;/strong&gt;。稍微留意一下，&lt;strong&gt;你什麼時候會評判別人？當你評判別人的時候，為什麼會有這些感覺？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;書中的例子如下：如果你評判說「追逐名利的人怎麼會快樂」，那可以知道名利對你可能不太重要；而如果評判「這傢伙只是個修沙發的，哪有可能多成功」則可以推論出地位跟讚揚可能對你很重要。&lt;strong&gt;微動力了解的越仔細越有幫助，掌握的越多越能夠有機會把他們互相結合&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;善加留意你自己的想法，去剖析自己，如此才能更了解自己。&lt;/p&gt;
&lt;p&gt;而找出動力之後，令我最驚訝的部分是：書中並不建議單純遵循著自己的熱情行事。相反的，應該要&lt;strong&gt;打造自己的熱情&lt;/strong&gt;。遵循熱情是很簡單的，然而，這些動力會隨著時間轉變。打造熱情就比較辛苦，必須&lt;strong&gt;主動地去整理、了解自己的動力，根據不同機會和要求去啟動不同的微動力&lt;/strong&gt;，如此長久下來的報酬將會相當可觀，並且也更有彈性。&lt;/p&gt;
&lt;p&gt;至少哪天你熱情燒盡不想幹了，你能知道你想做的其他動力，而不會茫然失措；而在你動力尚存時，你也能明白自己在做什麼，而能夠保持熱情。&lt;/p&gt;
&lt;h2 id=&#34;清楚你的選擇&#34;&gt;清楚你的選擇&lt;/h2&gt;
&lt;p&gt;標準化的制度下，有許多看似選擇，實際上卻是被揀選的情況。例如我們的大考，似乎是你考了分數，然後你主動選擇了這些學校；但實際上，是這些學校先選好要不要錄取你，你只能從被決定好的選項之中挑一個。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;選擇是一個主動的行為。你有選擇的自由，可以自己創造甚至沒有人考慮過的機會。&lt;/p&gt;
&lt;p&gt;挑揀則是一個被動的行為。你挑揀一個別人給的選項，等於是別人替你真正做了選擇，你只是從他們替你準備的巧克力盒裡挑一顆來吃。 -《黑馬思維》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;關於選擇和揀選的關係，我個人推薦這篇「&lt;a href=&#34;https://opinion.udn.com/opinion/story/6685/382755&#34;&gt;佩迪特的共和主義 —— 一種對自由的理解&lt;/a&gt;」，內文中舉例的馬兒、不被阻擾及不被干涉的差別，我認為和選擇與揀選的差別是一致的。&lt;/p&gt;
&lt;p&gt;當然，如同書中所述，有些人主張：如果選擇過多，就等於無法選擇。這是合理的，而書中的對應我認為相當精采易懂：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你是一隻鳥，你會選擇住哪？亞馬遜盆地的熱帶雨林？緯度很高的青藏高原？明尼蘇達州淒冷的湖邊？&lt;/p&gt;
&lt;p&gt;選項這麼多，要做出選擇還真是非常棘手。不過如果你是一隻鳥，做選擇一點也不難：你是哪種鳥，就選哪種棲地。 -《黑馬思維》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;按照你所了解的自己，你的特質、你的動力去做選擇就可以了。只要你足夠了解你自己，自然就能做出選擇，需要考量的問題只在於：&lt;strong&gt;你自身的微動力和這個選擇契不契合？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;標準化著重於成功的機率，而這是整體的機率，不是你的。你應該衡量你自己，考量你的動力，著重於你自己。掌握你自己，就能清楚你的選擇。&lt;/p&gt;
&lt;h2 id=&#34;了解你的策略&#34;&gt;了解你的策略&lt;/h2&gt;
&lt;p&gt;有在玩遊戲的人，不論是槍戰、棋類甚至動作遊戲，應該都很清楚一個道理：沒有最佳策略，只有當下最適合的策略；大方向儘管一致，但細節決定勝負。對於黑馬來說，當下最好的策略就是&lt;strong&gt;找出最適合你的策略&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;書中舉了相當多例子來展示策略的多樣性，例如魔術方塊的解法，品酒師對酒類的記憶法等等。這是相當合理的，要前往一個地方的路線本來就不只一種，最大的問題在於：&lt;strong&gt;我要怎麼知道哪個路線適合我？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;黑馬思維的答案是：&lt;strong&gt;嘗試&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;強項對我們而言是很模糊的，我們能知道想不想看電影，卻很難知道我們某個領域有多強。譬如說：你要怎麼知道你天生多會騎河馬？&lt;/p&gt;
&lt;p&gt;強項關乎情境，且持續變動。我們必須嘗試，才能了解我們大致上的水平。比對標準化要求的策略大多是堅持到底，黑馬要求多加嘗試。&lt;strong&gt;只有經由嘗試，才能掌握現有的狀況，也才能逐步修正策略&lt;/strong&gt;。就我來說，這和迭代開發有著異曲同工之妙。&lt;/p&gt;
&lt;h2 id=&#34;忽略你的目的地&#34;&gt;忽略你的目的地&lt;/h2&gt;
&lt;p&gt;本書認為目的地對自我實現是有害的，對於標準化來說，提供一個目的地有助於一致性，但對個人而言卻不一定如此。&lt;/p&gt;
&lt;p&gt;設定好的目的地會令人要求預計時間、要求進度、要求妥善規劃。然而現在的社會變動迅速，人生過程變數太多，每個人做同樣的事花費時間又不同，因此目的地的飄忽不定，反倒是一種束縛，綁死了你策略上的彈性，和可選擇的道路。&lt;/p&gt;
&lt;p&gt;與其決定成為某個行業，再去蒐集那些行業的特質，然後花費一個預期的時間前進；不如&lt;strong&gt;先確定自己的特質，再逐步前進，看自己的特質和策略能到達哪裡&lt;/strong&gt;。後者無疑更量身訂造。&lt;/p&gt;
&lt;p&gt;然而要注意！目的地和目標是不同的，我們用遠近，或是可不可見的界線去區分它。&lt;strong&gt;目標是你個人的選擇，相當具體，能夠採取行動；目的地則是大家口中的目標，難以預測，相當遙遠&lt;/strong&gt;，兩者其實在直覺上相當好區分：在截稿日前寫完，是目標；得到諾貝爾文學獎，則是目的地。&lt;/p&gt;
&lt;p&gt;其實我個人覺得這個概念很好接受，在前進的路上，你並不會挑個目的地然後就筆直撞過去，而是會朝某個大方向，一步一步地前進。書中用 &lt;a href=&#34;https://zhuanlan.zhihu.com/p/36902908&#34;&gt;梯度上升法&lt;/a&gt; 來舉例，每一步都選更高的方向前進，那麼就能夠走到山頂。（雖然在這邊看到這詞時總感覺有點妙就是了）&lt;/p&gt;
&lt;p&gt;本段其實是呼應最一開始的「他們並非為了達到卓越才實現自我，而是靠著追求自我實現而成就卓越」，從了解自己的動力，到不斷嘗試和修正策略。因此，如果能理解前面章節的內容，忽略目的地也是順其自然就會發生的事而已了。&lt;/p&gt;
&lt;h2 id=&#34;黑馬或是標準化&#34;&gt;黑馬或是標準化&lt;/h2&gt;
&lt;p&gt;書的後半篇幅多用於描述黑馬和標準化的差異，以及對現存制度的探討。儘管黑馬看起來人人都可以嘗試，但並不代表黑馬就得取代標準化，有些人在標準化的制度中更能取得優勢，那也是一種選擇。&lt;/p&gt;
&lt;p&gt;本書的重點在於了解，甚至接受每個人都可以有選擇：你可以為自己負責，你可以選擇體制或是黑馬甚至是更多的路線，你能發掘自身的動力，你能夠追求自我實現。如同書中所引的，每個人都有追求幸福的權利。&lt;/p&gt;
&lt;h2 id=&#34;心得&#34;&gt;心得&lt;/h2&gt;
&lt;p&gt;本來只想大略介紹一段，想不到翻著翻著就越寫越多，弄得書的介紹比心得還長上幾倍。儘管如此，我仍覺得我無法精準或是完整地表達黑馬思維所要傳達的意思，因此如果有興趣的話，還是非常推薦親自讀過一遍，自己讀過一次勝過閱讀十篇心得。&lt;/p&gt;
&lt;p&gt;黑馬思維的結構非常明確：&lt;strong&gt;掌握（自己的）各項情報、選定方向、建立選擇時的準則、制定遇到狀況的做法、逐步調整、並且在當下行動&lt;/strong&gt;。我覺得這並不是太特別的邏輯，甚至可以說是決策的通則。舉凡卡牌和戰略遊戲、行程規劃、開發專案或是各種領域，大抵都可以按照這個步驟去處理。&lt;/p&gt;
&lt;p&gt;以我之前喜歡的卡牌或爬塔遊戲為例：你得先了解你有哪些牌能用，你有越多的牌，能做的選擇和搭配就越多。接著就是分清楚，哪些牌值得使用、哪些牌可以組合使用、又該在什麼場合使用？這些牌所組成的目標牌組，主要的策略是什麼？正好與微動力、選擇和策略不謀而合。&lt;/p&gt;
&lt;p&gt;然而過程中的每一步能做到多少、能否判斷出最合適的選擇、策略是否經得起狀況的考驗，就是真正的差別。如果不去瞭解，你不會知道你的動力在哪；如果不去嘗試，你不會知道你的策略多可靠。&lt;/p&gt;
&lt;p&gt;之前聽過一句話：&lt;strong&gt;你個人就是一個品牌，你要把自己當成品牌去經營&lt;/strong&gt;。那麼，你就得要知道自己的優勢、知道自己的劣勢，知道其他人用的策略並不一定適合你，去嘗試分析適合你自己的模式。如此一來，才談得上是在經營自己。因此，誠摯地推薦可以嘗試去閱讀過這本黑馬思維，也許能因此找到新的路線也說不定呢。&lt;/p&gt;
&lt;p&gt;當然，我在看這本書的時候也不是沒有疑惑。例如說，研究那些已經成功的黑馬，算不算是一種倖存者偏差呢？後來終於想通，本書更多的是在於宣導一種理念，是希望每個人都能從中找到自己的動力，因此對於實踐的方式著墨甚少，例如&lt;a href=&#34;https://www.books.com.tw/products/0010822522&#34;&gt;我該如何打造我自己的熱情？&lt;/a&gt;或是&lt;a href=&#34;https://www.books.com.tw/products/0010752714&#34;&gt;我該如何掌握並提升我的強項？&lt;/a&gt;這些都是可以進一步討論的部分。但整體來說，黑馬思維值得一讀，也許之後還會再整理相關的心得吧！下次見。&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀&#34;&gt;延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://readingoutpost.com/dark-horse/&#34;&gt;《黑馬思維》讀書心得：尋找熱情前必問自己這6個問題&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/%E4%B8%80%E5%80%8B%E4%BA%BA%E7%9A%84%E6%96%87%E8%97%9D%E5%BE%A9%E8%88%88/%E9%BB%91%E9%A6%AC%E6%80%9D%E7%B6%AD-%E6%91%98%E8%A6%81-d6bdeedc5aae&#34;&gt;《黑馬思維》摘要&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>C#: 位元旗標 (Bit flag) 與列舉</title>
      <link>https://igouist.github.io/post/2020/06/bit-flags-and-enum/</link>
      <pubDate>Sun, 14 Jun 2020 21:27:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/06/bit-flags-and-enum/</guid>
      <description>&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#enum--flags&#34;&gt;Enum &amp;amp; Flags&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#查詢--and&#34;&gt;查詢 =&amp;gt; AND&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#賦予--or&#34;&gt;賦予 =&amp;gt; OR&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#移除--xor&#34;&gt;移除 =&amp;gt; XOR&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#多項賦予--or-or-or&#34;&gt;多項賦予 =&amp;gt; OR, OR, OR&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#列出內容--foreach-flags&#34;&gt;列出內容 =&amp;gt; Foreach, Flags&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#延伸閱讀及參考資料&#34;&gt;延伸閱讀及參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;p&gt;前陣子碰到個資料表儲存方式，因為這種位元運算的方式也常用在權限管理等地方，這邊就順手紀錄一下。&lt;/p&gt;
&lt;p&gt;平常遇到二元的情形（例如 開／關、有／沒有），我們會直接宣告個 Boolean 來處理。例如 &lt;code&gt;bool isNice = false&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但這次遇到的是同時有多組「有／沒有」的狀況，而我遇到的程式碼並沒有分成多個 Bool 去做處理，而是直接儲存成一個數值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這是因為「有／沒有」只佔據一個位元，那麼將多個狀況按照位元順序排列的話，就只需要一個數字就可以紀錄並傳遞給其他系統了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如最常見的權限系統：假設我們有「讀」、「寫」、「執行」的權限，接著就可以按照這個順序，用 1 和 0 來代表「有／沒有」來排列位元，&lt;/p&gt;
&lt;p&gt;也就是說，如果權限是 &lt;code&gt;讀＝可、寫＝不可、執行＝可&lt;/code&gt; 的時候，就記做 &lt;code&gt;101&lt;/code&gt; = &lt;code&gt;5&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;這種直接用一組位元來表示狀態的方式，就叫做&lt;strong&gt;位元旗標（Bit flag）&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1 讀   = 可
0 寫   = 不可
1 執行 = 可

/* 橫放 */
=&amp;gt; 101 (2進位) 
=&amp;gt; 5 
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;enum--flags&#34;&gt;Enum &amp;amp; Flags&lt;/h2&gt;
&lt;p&gt;假使某天老闆靈光一現，決定接下來的新人員工都要記錄他們會的程式語言，並且他們報到的時候就會發一張公司列好的程式語言清單請他們勾選。這時候要怎麼做呢？&lt;/p&gt;
&lt;p&gt;在 C# 中已經有方便的工具可以處理位元旗標，我們可以建一組叫做 SkillEnum 的&lt;strong&gt;列舉（Enum）&lt;/strong&gt;，並且按照上面說明的，將老闆提到的每個技能各自用一個位元來表示。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;[Flags]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; SkillEnum
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    C = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,     &lt;span style=&#34;color:#75715e&#34;&gt;// 0001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PHP = &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,   &lt;span style=&#34;color:#75715e&#34;&gt;// 0010&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SQL = &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;,   &lt;span style=&#34;color:#75715e&#34;&gt;// 0100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Java = &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;// 1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;註：&lt;code&gt;[Flags]&lt;/code&gt;的標籤是指 C# 專門提供給位元旗標使用的 Enum，請參見 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.flagsattribute?view=netcore-3.1&#34;&gt;FlagsAttribute&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;只要在 enum 上加上 Flags 的屬性，除了自動按照 2 的次元增加以外，在使用 ToString() 也能更方便看見旗標內容&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;有了這個列舉之後，我們就可以&lt;strong&gt;表達不同排列組合的狀況&lt;/strong&gt;了。例如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;* C: C
* P: PHP
* S: SQL
* J: Java
===========
J S P C
0 0 0 0 =&amp;gt; 什麼都不會
0 0 0 1 =&amp;gt; 只會 C
0 0 1 1 =&amp;gt; 同時會 C 和 PHP
1 0 1 0 =&amp;gt; 同時 PHP 和 Java
1 1 1 1 =&amp;gt; 全部都會
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;查詢--and&#34;&gt;查詢 =&amp;gt; AND&lt;/h2&gt;
&lt;p&gt;那麼假使今天來的新人 Bob，他會 C, PHP 兩種語言，那現在他的資料欄位，就會是這兩種語言對應的位置為 1，其餘為 0，也就是 &lt;code&gt;0011&lt;/code&gt; =  &lt;code&gt;3&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; BobSkills = &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// var BobSkills = 0b_0011; // C + PHP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;當 &lt;strong&gt;查詢&lt;/strong&gt; 的時候，就將 Bob 的數值和目標數值做 &lt;strong&gt;&lt;code&gt;AND&lt;/code&gt;&lt;/strong&gt; 運算。例如說我們想知道 Bob 會不會 C，就可以將 &lt;code&gt;3&lt;/code&gt;（二進位 &lt;code&gt;0011&lt;/code&gt;） 和代表 C 的 &lt;code&gt;1&lt;/code&gt; （二進位 &lt;code&gt;0001&lt;/code&gt;） 做 &lt;code&gt;&amp;amp;&lt;/code&gt;，即可知道 Bob 會不會 C。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    J S P C
    0 0 1 1 Bob
AND 0 0 0 1 SkillEnum.C
-----------
    0 0 0 1 =&amp;gt; 1 &amp;gt; 0 =&amp;gt; True
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    J S P C
    0 0 1 1 Bob
AND 1 0 0 0 SkillEnum.Java
-----------
    0 0 0 0 =&amp;gt; 0 =&amp;gt; False
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;也就是必須要 &lt;strong&gt;同個位置的值都是 1&lt;/strong&gt;，在這邊就是指 Bob 的技能中 Java 的欄位，和 SkillEnum 中對應的欄位都要是 1 的時候，才會有數值。否則就會是 0：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; BobSkills = &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// var BobSkills = 0b_0011; // C + PHP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 確認是否有某個 Flag，使用 And(&amp;amp;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isBobKnowC = BobSkills &amp;amp; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.C;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 會使用 C 嗎？{Convert.ToBoolean(isBobKnowC)}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isBobKnowJava = BobSkills &amp;amp; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.Java;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 會使用 Java 嗎？{Convert.ToBoolean(isBobKnowJava)}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;賦予--or&#34;&gt;賦予 =&amp;gt; OR&lt;/h2&gt;
&lt;p&gt;那假設 Bob 經過一番苦練，又掌握了 SQL 這門語言呢？當我們要 &lt;strong&gt;賦予&lt;/strong&gt; 的時候，就需要用 &lt;strong&gt;&lt;code&gt;OR&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是當 &lt;strong&gt;兩者之間任一為 1&lt;/strong&gt; 的時候，在這邊也就是當 Bob 的技能和我們要求給他的 SkillEnum 同位置只要有一個是 1，那就會有數值：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    J S P C
    0 0 1 1 Bob
 OR 0 1 0 0 SkillEnum.SQL
-----------
    0 1 1 1 =&amp;gt; New Bob
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;例如說原本 Bob 不會 SQL，所以 SQL 那一欄就會是 0 。而我們把它和 SkillEnum.SQL（也就是只有 SQL 那一欄是 1 ）做 OR 運算後，接著我們只要將運算好的結果再賦值給 Bob，這樣 Bob 的 SQL 欄位就會變成 1 了，同時也不會影響到其他欄位。&lt;/p&gt;
&lt;p&gt;現在我們就可以用 &lt;code&gt;OR&lt;/code&gt; 把 SQL 的技能傳授給 Bob：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isBobKnowSQL = BobSkills &amp;amp; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.SQL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 會使用 SQL 嗎？{Convert.ToBoolean(isBobKnowSQL)}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 賦予某個 Flag，使用 OR (|)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BobSkills = BobSkills | (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.SQL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;（Bob 學習 SQL 中）&amp;#34;&lt;/span&gt;.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;isBobKnowSQL = BobSkills &amp;amp; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.SQL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 會使用 SQL 嗎？{Convert.ToBoolean(isBobKnowSQL)}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;移除--xor&#34;&gt;移除 =&amp;gt; XOR&lt;/h2&gt;
&lt;p&gt;經過了很久很久以後，Bob 已經忘記當年學的 SQL 怎麼寫了。我們又要怎麼把他的 SQL 這項技能給拿掉呢？&lt;/p&gt;
&lt;p&gt;當我們要 &lt;strong&gt;移除&lt;/strong&gt; 某一項旗標的時候，只需要使用 &lt;strong&gt;&lt;code&gt;XOR&lt;/code&gt;&lt;/strong&gt; 就行了。&lt;strong&gt;&lt;code&gt;XOR&lt;/code&gt; 是指互斥，就像磁碟兩極一樣。當兩者不同為 1，若相同時則為 0&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此當 Bob 代表 SQL 的欄位為 1 的時候，我們再將 SQL 為 1 的 的數值丟進去做 XOR，就可以把&lt;strong&gt;兩者同時為 &lt;code&gt;1&lt;/code&gt; 的欄位給變回 0&lt;/strong&gt;，並且讓原本為 1 的欄位持續為 1，原本為 0 的欄位持續為 0，達到移除指定目標的效果。&lt;/p&gt;
&lt;p&gt;但在使用上要注意，必須&lt;strong&gt;先確認目標欄位的確有數值&lt;/strong&gt;，也就是 Bob 是真的已經會 SQL，&lt;strong&gt;否則&lt;/strong&gt;若原本不會 (0) 的將不小心學會 (0 XOR 1 =&amp;gt; 1)。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    J S P C
    0 1 1 1 Bob
XOR 0 1 0 0 SkillEnum.SQL
-----------
    0 0 1 1 =&amp;gt; New Bob

// 警告：使用 XOR 之前一定要先檢查，若原本是關閉 (0) 的將會被打開 (0 XOR 1 =&amp;gt; 1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;現在我們就讓 Bob 忘記他曾經學過的 SQL：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;isBobKnowSQL = BobSkills &amp;amp; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.SQL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 會使用 SQL 嗎？{Convert.ToBoolean(isBobKnowSQL)}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 挪除某個 Flag，使用 XOR (^)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BobSkills = BobSkills ^ (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.SQL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;（Bob ... 忘記了SQL！）&amp;#34;&lt;/span&gt;.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;isBobKnowSQL = BobSkills &amp;amp; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.SQL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 會使用 SQL 嗎？{Convert.ToBoolean(isBobKnowSQL)}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;多項賦予--or-or-or&#34;&gt;多項賦予 =&amp;gt; OR, OR, OR&lt;/h2&gt;
&lt;p&gt;隨著 Bob 逐漸老去，公司也招來了新員工。如今換成 Bob 來幫他維護技能表了，那我們要怎麼用 SkillEnum 給這個菜雞&lt;strong&gt;預設值&lt;/strong&gt;呢？&lt;/p&gt;
&lt;p&gt;聰明的你應該能猜出其實這也就是賦值！只要把&lt;strong&gt;有的項目全部 OR 起來&lt;/strong&gt;就可以了，這位新菜雞他會 C, SQL, Java：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    J S P C
    0 0 0 0 Newbie
 OR 0 0 0 1 SkillEnum.C
 OR 0 1 0 0 SkillEnum.SQL
 OR 1 0 0 0 SkillEnum.Java
-----------
    1 1 0 1 =&amp;gt; 13 =&amp;gt; Newbie&amp;#39;s Skills
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那麼現在就讓我們用 C# 實作：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 賦與多個值 ＝ 一路 OR 下去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; NewbieSkills =    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.C |   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.SQL |     
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)SkillEnum.Java; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 技能欄的十進位為：{NewbieSkills}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 13&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Bob 技能欄的二進位為：{Convert.ToString(NewbieSkills, 2)}&amp;#34;&lt;/span&gt;.Dump(); &lt;span style=&#34;color:#75715e&#34;&gt;// 1101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;列出內容--foreach-flags&#34;&gt;列出內容 =&amp;gt; Foreach, Flags&lt;/h2&gt;
&lt;p&gt;那如果我想要確認現在有哪些欄位是開啟的呢？例如說，當我們要確認 Bob 會哪些程式語言的時候怎麼做呢？&lt;/p&gt;
&lt;p&gt;既然用 AND 可以查詢其中一個位置，那麼只要將列舉和位元用迴圈逐一 AND 出來，就可以還原 Bob 的列表囉&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; enumCount = Enum.GetNames(&lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt;(SkillEnum)).Count();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; NewbieSkillsList = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; i = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &amp;lt; enumCount; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(Convert.ToBoolean(Bob2Skills &amp;amp; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)Math.Pow(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, i))) &lt;span style=&#34;color:#75715e&#34;&gt;// AND 運算&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        NewbieSkillsList.Add(((SkillEnum)Math.Pow(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, i)).ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Newbie 會的語言有：{String.Join(&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;, NewbieSkillsList)}&amp;#34;&lt;/span&gt;.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// C, SQL, Java&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不過上面這個依序列印的方式還是太麻煩了。如果有像前半段提到的，&lt;strong&gt;替 Enum 加上 &lt;code&gt;[Flags]&lt;/code&gt; 標籤&lt;/strong&gt;的話，用起來就更簡單了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;$&amp;#34;Newbie 掌握的技能為：{(SkillEnum)NewbieSkills}&amp;#34;&lt;/span&gt;.Dump(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// C, SQL, Java&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後再總結一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;檢查的時候用 &lt;code&gt;AND&lt;/code&gt; 找出目標的位置是否為 1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;賦予內容時則用 &lt;code&gt;OR&lt;/code&gt; 讓指定的位置變成 1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移除的時候則用 &lt;code&gt;XOR&lt;/code&gt; 讓目標位置的 1 抵銷為 0&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整理來說概念並不困難，只是一個位元對應一個對象，再視情況進行運算而已。但能應用的範圍相當廣泛，除了最常用的權限管理，其他諸如活動月份、門鎖狀況等等只要符合條件的情形都可以借這個方法來處理。這邊稍作紀錄，希望以後能派上用場。&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀及參考資料&#34;&gt;延伸閱讀及參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@kazafchen/%E4%B8%80%E4%BB%B6intent%E6%95%99%E6%88%91%E7%9A%84%E4%BA%8B-bitwise-operation-dd3a388ae34e&#34;&gt;一件Intent教我的事： Bitwise Operation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/700165/&#34;&gt;JavaScript中的位運算和權限設計&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;補一下原出處： &lt;a href=&#34;https://juejin.im/post/5dc36f39e51d4529ed292910&#34;&gt;JavaScript 中的位运算和权限设计 - 掘金&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://shunnien.github.io/2017/06/27/byte-operator-auth/&#34;&gt;位元運算與權限控制 - ShunNien&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2019/02/java-bit-flag.html&#34;&gt;Java bit flag - 菜鳥工程師肉豬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.zhihu.com/question/34021773&#34;&gt;在写代码的过程中使用位运算的好处？ - 知乎&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jianshu.com/p/262652908eb8&#34;&gt;位运算和权限管理系统 - 簡書&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blogs.uuu.com.tw/Articles/post/2017/06/14/%E4%BD%BF%E7%94%A8%E5%88%97%E8%88%89%E8%88%87%E6%97%97%E6%A8%99%E8%A8%AD%E8%A8%88%E5%A4%9A%E9%81%B8.aspx&#34;&gt;使用列舉與旗標設計多選 - ProgrammerXDB Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zh.wikipedia.org/wiki/Chmod&#34;&gt;Chmod - 維基百科&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.podkalicki.com/bit-level-operations-bit-flags-and-bit-masks/&#34;&gt;Bit-level operations – bit flags and bit masks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>WakaTime —— 我 Coding 了多久？</title>
      <link>https://igouist.github.io/post/2020/06/wakatime/</link>
      <pubDate>Sun, 07 Jun 2020 22:10:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/06/wakatime/</guid>
      <description>&lt;p&gt;這一周又是 &lt;del&gt;偷懶週&lt;/del&gt; 繁忙週，因此就跟大家分享一個有趣的網站：&lt;a href=&#34;https://wakatime.com/&#34;&gt;WakaTime&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;WakaTime 能夠幫你自動記錄寫程式的時間、常用語言及專案，只要簡單地在常用的 IDE 設定好擴充套件就可以達成。 WakaTime 支援的 IDE 可以參閱 &lt;a href=&#34;https://wakatime.com/plugins&#34;&gt;WakaTime - Plugin&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;註冊相當簡單，此處就不贅述。稍微介紹一下儀表板的各區塊的功能：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/wd1LZLr.webp&#34;
  alt=&#34;&#34;width=&#34;844&#34; height=&#34;239&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

左上會有最近的專案及花費時間，每個色塊都是一個專案，滑鼠移上去就會顯示專案名稱。&lt;/p&gt;
&lt;p&gt;點擊其中一天更會顯示當天的時間軸，可說是無所遁形。至於它判斷專案名稱的方法是看你編輯的檔案所在的資料夾名稱。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/2icNzlL.webp&#34;
  alt=&#34;&#34;width=&#34;817&#34; height=&#34;216&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

右上是編寫和測試的所佔時間，不過我在使用的時候常常抓不到測試和建置的時間，因此沒在意過。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ic7NGAE.webp&#34;
  alt=&#34;&#34;width=&#34;826&#34; height=&#34;221&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

正左則是當天的時間軸和某幾段時間正在處理的專案，可以清楚看到大概哪個時段在認真 Coding（因為都是公司專案，這邊就先碼一下）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Qzu0ucB.webp&#34;
  alt=&#34;&#34;width=&#34;817&#34; height=&#34;214&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

正右則是達到目標的比例，WakaTime 可以到左側選單的 &lt;code&gt;Goal&lt;/code&gt; 設定個人目標，例如每週幾個小時，就可以在這邊看自己當天的達成率如何。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nu640EP.webp&#34;
  alt=&#34;&#34;width=&#34;816&#34; height=&#34;208&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

左下則是最近常使用的語言和比例，主要是抓取編輯中檔案的副檔名自動判定。另外也能到 &lt;code&gt;Settings -&amp;gt; Custom Rules&lt;/code&gt; 去設定個人化的規則，例如我個人就會將 .ipynb 計時到 Python 中。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Bu5df8N.webp&#34;
  alt=&#34;&#34;width=&#34;812&#34; height=&#34;209&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

右下就很直覺，就是紀錄常用的 IDE 的時間，像我個人挺偏食的，就不常看到這裡。不過一些用瀏覽器的 Coding 方式就不太適用，像我常用的 Jupyter notebook 基本上就是抓不到，許願希望之後的 Chrome 能支援。&lt;/p&gt;
&lt;p&gt;除了上面這些區塊，往下拉也可以看到最近的專案耗費的時間，可以看看最近耗費比較多的專案有哪些。&lt;/p&gt;
&lt;h2 id=&#34;環境設定&#34;&gt;環境設定&lt;/h2&gt;
&lt;p&gt;WakaTime 主打的就是輕便、自動計時。因此設定步驟也相當簡單，各 IDE 的設定步驟可以從 &lt;a href=&#34;https://wakatime.com/plugins&#34;&gt;WakaTime - Plugin&lt;/a&gt; 裡點選自己用的 IDE 進去看安裝介紹。這邊就以我最常用的 Visual Studio 做示範。&lt;/p&gt;
&lt;p&gt;首先點選 Wakatime 右上角的 個人頭像，進入 Settings，確認左側在 Account，這時應該能看到自己的 API Key，點選顯示之後先複製下來。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pvbaKAT.webp&#34;
  alt=&#34;&#34;width=&#34;766&#34; height=&#34;240&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;之後打開你的 Visual Studio，上方工具列點選 &lt;code&gt;延伸模組 -&amp;gt; 管理延伸模組&lt;/code&gt;，開啟模組管理員之後選擇 線上，搜尋 Wakatime 並安裝。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Aj6q2XL.webp&#34;
  alt=&#34;&#34;width=&#34;939&#34; height=&#34;655&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;重啟 Visual studio 並安裝完畢後，再度打開時應該會自動跳出設定畫面；如果沒有跳出來，也可以從上方工具列點選 &lt;code&gt;工具 -&amp;gt; WakaTime -&amp;gt; Settings&lt;/code&gt; 進入設定畫面&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Agrd4QS.webp&#34;
  alt=&#34;&#34;width=&#34;466&#34; height=&#34;182&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;設定畫面如下，將剛剛複製的 API Key 貼上。如果是在公司或是有需要設定 Proxy 的地方記得也順便填一下&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/eFCfNv4.webp&#34;
  alt=&#34;&#34;width=&#34;766&#34; height=&#34;260&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就設定完囉！是不是很簡單呢？之後當使用 Visual Studio 寫程式的時候，就會自動記下 時間、程式類型、資料夾名稱並傳到 Wakatime 做統計囉&lt;/p&gt;
&lt;p&gt;當然，對於背景紀錄資料有疑慮的朋友，也可以直接翻 &lt;a href=&#34;https://github.com/wakatime&#34;&gt;WakaTime 的 Github&lt;/a&gt;。這個套件是完全開源的。如果嫌使用擴充太無聊或是想自己開發個比較好看的儀表板的朋友，也可以參閱 &lt;a href=&#34;https://wakatime.com/developers&#34;&gt;Wakatime 的開發者頁面&lt;/a&gt;呦。&lt;/p&gt;
&lt;p&gt;最後關於紀錄的部分，之前有看到實測說單純掛網走人是不會算時數的。不過會這樣做的人，本來也就沒什麼動機去用這類時間記錄的工具吧！&lt;/p&gt;
&lt;h2 id=&#34;後記&#34;&gt;後記&lt;/h2&gt;
&lt;p&gt;要注意，免費用戶只能看最近 14 天的紀錄，要看完整紀錄就必須 &lt;a href=&#34;https://wakatime.com/pricing?utm_source=magic-panda-engineer&#34;&gt;付費&lt;/a&gt;，我個人因為是看心情愉快的，因此免費版已經很符合我的需求。若是使用多個 IDE 或想當成時數管理的朋友，又或者是想戰 &lt;a href=&#34;https://wakatime.com/leaders&#34;&gt;排行榜&lt;/a&gt; 的朋友，可以使用一陣子再考慮囉。但要強調，紀錄時間只是做為參考使用，並非工作的標準喲！畢竟弄文件跟查 Bug 更花時間嘛。&lt;/p&gt;
&lt;p&gt;雖然又水了一篇，不過也是挺想把這東西介紹給大家用用看。雖然我個人做時間紀錄大多是使用 &lt;a href=&#34;https://www.playpcesor.com/2016/11/toggl-time-track.html&#34;&gt;Toggl&lt;/a&gt; 來計時，不過偶而開 WakaTime 看看自己最近花多少時間在 Coding？都在寫什麼？專案之間的時間分配得如何？就有種更能掌握自己最近都在忙些什麼的感覺。有時看到柱狀圖一排高聳，或是連續幾周達成目標，就會更有戰鬥力的感覺；若是發現最近時數低落，也會心虛想要彌補一點。如此也是挺有一番趣味，共勉之。&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀&#34;&gt;延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/code-and-me/%E7%94%A8-wakatime-%E8%87%AA%E6%88%91%E7%9B%A3%E6%8E%A7-f59599144e28&#34;&gt;用 WakaTime 自我監控&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://magic-panda-engineer.github.io/tools/wakatime-for-time-management&#34;&gt;利用 Wakatime 來掌握寫程式的時間&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://demo.tc/post/visual%20studio%20%E5%B7%A5%E4%BD%9C%E6%99%82%E9%96%93%E8%A8%98%E9%8C%84%E5%A5%97%E4%BB%B6%20-%20codealike&#34;&gt;Visual Studio 工作時間記錄套件 - Codealike&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2018/11/rescuetime.html&#34;&gt;RescueTime 拯救時間利器上手教學，自動時間記錄與生產力評分&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>我要訂便當 (5): Heroku 填坑小記</title>
      <link>https://igouist.github.io/post/2020/05/bandon-5-heroku-debug/</link>
      <pubDate>Sun, 31 May 2020 23:24:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/05/bandon-5-heroku-debug/</guid>
      <description>&lt;p&gt;在上一集的 &lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;將 Python 腳本部署上 Heroku&lt;/a&gt; 中，記錄了將 Python 腳本放上 Heroku 的過程，但仍然沒有將我們的 &lt;a href=&#34;https://igouist.github.io/tags/bandon/&#34;&gt;訂便當小幫手&lt;/a&gt; 給放上去。之前有稍微提到是因為過程中遇到了一些問題，最後決定將這些問題和找到的前人解決文記錄在這一篇，將來再遇到的時候就可以參考。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SQLite 要改成用 PostgreSQL&lt;/li&gt;
&lt;li&gt;在 Heroku 上執行 Selenium&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;一sqlite-要改成用-postgresql&#34;&gt;一、SQLite 要改成用 PostgreSQL&lt;/h2&gt;
&lt;p&gt;在之前的 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;我要訂便當(2) —— 用 Python + Sqlite 儲存訂單&lt;/a&gt; 中，為了方便及簡單性，選擇了較輕便的 SQLite 來儲存我們爬回來的訂單資訊。然而，SQLite 將資料儲存在小檔案以便於攜帶的做法，在 Heroku 上將會遭遇像是各個 dyno 的資料不同步等許多問題，因此 Heroku 是不建議使用 SQLite 的。&lt;/p&gt;
&lt;p&gt;在 Heroku 的開發文件中的 &lt;a href=&#34;https://devcenter.heroku.com/articles/sqlite3&#34;&gt;SQLite on Heroku&lt;/a&gt; 有關於這部分的詳細說明，並直接提到「&lt;em&gt;If you were to use SQLite on Heroku, you would lose your entire database at least once every 24 hours.&lt;/em&gt;」同時，官方也提供了他們的建議：&lt;strong&gt;PostgreSQL&lt;/strong&gt;。相較於輕便但只能同時單一寫入而且還會在 dyno 炸掉的 SQLite，PostgreSQL 的完整性更符合 Heroku 對服務的要求。&lt;/p&gt;
&lt;p&gt;關於在 Heroku 上使用 PostgreSQL 的做法，這篇 &lt;a href=&#34;https://swf.com.tw/?p=1327&#34;&gt;佈署 Python Flask 網站留言板應用程式到 Heroku + PostgreSQL 資料庫系統&lt;/a&gt; 說明得相當詳細。&lt;/p&gt;
&lt;p&gt;這邊簡述一下操作過程（當然，還是建議進入上面推薦的網頁直接照圖操作）&lt;/p&gt;
&lt;p&gt;首先，先進到專案的頁面，選擇 Resources 分頁並往下到 Add-on，擴充套件基本上都從這裡開始安裝，事實上遇到的許多問題都會到這裡用擴充套件解決。在此就先輸入 Postgres 並安裝 Heroku Postgres，並且選擇免費方案。&lt;/p&gt;
&lt;p&gt;安裝完畢之後，可以點選 Heroku Postgres 進入擴充套件管理的頁面，這邊進去 Setting 就可以進行相關的設定，也能從這邊取得帳號、密碼及資料庫URL。&lt;/p&gt;
&lt;p&gt;Heroku 部分設定完畢之後，就要回來改我們便當的程式碼。相對於當初 SQLite 時使用的 sqlite3 包，這次要操作 PostgreSQL 則是要使用 python-psycopg2 這個包。&lt;/p&gt;
&lt;p&gt;相關的操作可以參考 Gitbook.net 的這篇 &lt;a href=&#34;http://tw.gitbook.net/postgresql/2013080998.html&#34;&gt;PostgreSQL 連接 Python&lt;/a&gt;。可以發現整體的操作方式和之前使用 sqlite3 並沒有什麼太大的不同，因此略做修改之後就可以使用囉。&lt;/p&gt;
&lt;h3 id=&#34;本區段的參考資料&#34;&gt;本區段的參考資料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://swf.com.tw/?p=1327&#34;&gt;佈署 Python Flask 網站留言板應用程式到 Heroku + PostgreSQL 資料庫系統&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10199016&#34;&gt;[Ting&amp;rsquo;s筆記Day5] 在部署到Heroku之前，將Rails專案從SQLite設定為PostgreSQL - iT邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://tw.gitbook.net/postgresql/2013080998.html&#34;&gt;PostgreSQL 連接 Pytho - Gitbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://devcenter.heroku.com/articles/sqlite3&#34;&gt;SQLite on Heroku - Heroku Dev Center&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;二在-heroku-上執行-selenium&#34;&gt;二、在 Heroku 上執行 Selenium&lt;/h2&gt;
&lt;p&gt;前面的 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt; 中，能取得訂單內容主要都是依賴於 Selenium 控 chrome 上。然而我們丟上 Heroku 後… 哪來的瀏覽器啊！因此遭遇到了這整個專案最大的存在危機（？）&lt;/p&gt;
&lt;p&gt;幸好後來有找到這篇 &lt;a href=&#34;https://medium.com/@mikelcbrowne/running-chromedriver-with-python-selenium-on-heroku-acc1566d161c&#34;&gt;Running ChromeDriver with Python Selenium on Heroku&lt;/a&gt;，裡面有說明如何順利在 Heroku 上跑 Selenium，實在是相當感謝，這邊也記錄一下。&lt;/p&gt;
&lt;p&gt;我們在前面 &lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;我要訂便當(4-1) —— 將 Python 腳本部署上 Heroku&lt;/a&gt; 有提到，Heroku 有建置包這東西來協助建置專案，也提到 Python 是 heroku-buildpack-python。這邊就是建置包去建出 webdriver，因此使用 &lt;code&gt;heroku buildpacks:add&lt;/code&gt; 的指令去加入 &lt;code&gt;heroku-buildpack-chromedriver heroku buildpa&lt;/code&gt; 這個建置包。&lt;/p&gt;
&lt;p&gt;加入完畢之後，要設定 heroku 的 config 去指定 chrome 和 chrome driver 的路徑&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;heroku config:set GOOGLE_CHROME_BIN=/app/.apt/usr/bin/google_chrome
heroku config:set CHROMEDRIVER_PATH=/app/.chromedriver/bin/chromedriver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最後只要修改程式就可以囉，一樣先指定好路徑&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GOOGLE_CHROME_PATH &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/app/.apt/usr/bin/google_chrome&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CHROMEDRIVER_PATH &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/app/.chromedriver/bin/chromedriver&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;並且在設定 selenium 的部份加上一些 chrome 的參數&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chrome_options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--disable-gpu&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chrome_options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;--no-sandbox&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chrome_options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;binary_location &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; GOOGLE_CHROME_PATH
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在初始化 &lt;code&gt;webdriver.Chrome&lt;/code&gt; 一併傳入就可以囉！說起來之前 chrome 被我改壞時，也是靠 -no-sandbox 才開起來的，關於 sandbox ，可以看 Google 的 &lt;a href=&#34;https://www.google.com/googlebooks/chrome/med_26.html&#34;&gt;這篇漫畫&lt;/a&gt;，或是這篇 &lt;a href=&#34;https://www.inside.com.tw/article/14031-google-chrome-browser-10-years-history&#34;&gt;Inside 的簡單介紹&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;本區段的參考資料-1&#34;&gt;本區段的參考資料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@mikelcbrowne/running-chromedriver-with-python-selenium-on-heroku-acc1566d161c&#34;&gt;Running ChromeDriver with Python Selenium on Heroku - Michael Browne - Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://b0212066.pixnet.net/blog/post/213602412-heroku-%E4%BD%BF%E7%94%A8-selenium-%E7%9A%84-webdriver&#34;&gt;Heroku 上使用 webdriver 爬蟲抓資料 - Kevin的部落格&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/41059144/running-chromedriver-with-python-selenium-on-heroku&#34;&gt;Running ChromeDriver with Python selenium on Heroku - Stackoverflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;結語&#34;&gt;結語&lt;/h2&gt;
&lt;p&gt;訂便當系列到這邊也告一段落了。事實上到了中後期，為了配合公司環境已經把爬蟲放在公司跑，並改成拿去接公司指定的通訊軟體，Line notify 這邊就比較少繼續碰了。中間遇到的一些問題也是有空加減解的程度，因此暫時沒有繼續更新訂便當的打算了。&lt;/p&gt;
&lt;p&gt;但 Heroku 實在是挺方便，之後應該還是會嘗試把其他東西丟上來跑看看，也因此還是覺得要把這些小問題記錄下來，之後如果遇到別的相關問題，也比較方便集中起來，因此就有了這兩篇的紀錄。&lt;/p&gt;
&lt;p&gt;那麼本週就到這裡，之後還會找新的坑來跳，畢竟菜雞就是不斷地踩坑嘛！感謝收看～&lt;/p&gt;
&lt;h2 id=&#34;我要訂便當系列&#34;&gt;我要訂便當系列&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;我要訂便當(2) —— 用 Python + Sqlite 儲存訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;我要訂便當(3) —— 用 Python + Line Notify 傳送通知&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;我要訂便當(4) —— 將 Python 腳本部署上 Heroku&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-5-heroku-debug/&#34;&gt;我要訂便當(5) —— Heroku 填坑小記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Json View —— 用 Chrome 打開 Json 的正確方式</title>
      <link>https://igouist.github.io/post/2020/05/jsonview/</link>
      <pubDate>Sun, 17 May 2020 15:39:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/05/jsonview/</guid>
      <description>&lt;p&gt;本來這週已經沒梗了，不過同學來問了個接 API 的問題還附了照片，頓時決定順手推一下好用的瀏覽器插件：&lt;a href=&#34;https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc&#34;&gt;Json View&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Json View 是一款 Chrome 的擴充套件，能把 Json 格式的資料重新排版，保護妳我的眼睛。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：關於 JSON 可以參閱 &lt;a href=&#34;http://miniaspreading.github.io/guide-to-json/1-what-is-json.html&#34;&gt;JSON精要讀書紀錄&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;照慣例來張效果圖鎮樓



&lt;img
  src=&#34;https://image.igouist.net/VYg1Siy.webp&#34;
  alt=&#34;&#34;width=&#34;796&#34; height=&#34;737&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;該同學傳來的圖片是長這樣的（示意圖，非當事 API，用 UBike 開放資源臨演）



&lt;img
  src=&#34;https://image.igouist.net/kaoOwTt.webp&#34;
  alt=&#34;&#34;width=&#34;1214&#34; height=&#34;448&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;突然還以為咱們要玩威利在哪裡，這不是欺負我眼睛不好嘛。&lt;/p&gt;
&lt;p&gt;但畢竟只是小問題，不能開口就要人家給 Swagger 或用個 Postman（API 測試神器，之後也來推薦一篇），用圖片描述也是無可厚非，但還是必須顧眼睛。因此現場直接就推薦同學安裝這款 chrome 插件：&lt;a href=&#34;https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc&#34;&gt;Json View&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;安裝之後再度使用瀏覽器打開 API，就會是長這樣的：



&lt;img
  src=&#34;https://image.igouist.net/TgKKIUK.webp&#34;
  alt=&#34;&#34;width=&#34;737&#34; height=&#34;681&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;經過自動排版之後，這不是好看多了！&lt;/p&gt;
&lt;p&gt;這邊真心推薦用 Chrome 的朋友，就先裝下來吧；至於火狐似乎原本就會幫忙整理排版了；用 IE 的話，就當我沒說。另外也要感謝親愛的同學，&lt;del&gt;又讓我水了一篇&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;ps: 最後效果跟鎮樓圖有差別？因為我是黑色主題的愛好者，所以這就再推一個擴充套件囉：&lt;a href=&#34;https://chrome.google.com/webstore/detail/dark-reader/eimadpbcbfnmbkopoojfekhnkhdbieeh&#34;&gt;Dark Reader&lt;/a&gt;。謝謝觀看，咱們下次見～&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Carbon —— 把程式碼片段截成有質感的圖片吧</title>
      <link>https://igouist.github.io/post/2020/05/carbon/</link>
      <pubDate>Sun, 10 May 2020 23:57:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/05/carbon/</guid>
      <description>&lt;p&gt;開始挑戰每週寫文之後，發現最大的敵人根本不是寫文這件事，而是自己的惰性啊。像這次母親節假期過太爽，差點兒就忘記發文…… 所以這週就還是 &lt;del&gt;偷懶&lt;/del&gt; 工具介紹囉。&lt;/p&gt;
&lt;p&gt;今天要分享的是 &lt;a href=&#34;https://carbon.now.sh/&#34;&gt;Carbon&lt;/a&gt; 這個工具，它能夠&lt;strong&gt;將貼上的程式碼輸出成圖片，並且支援程式碼上色&lt;/strong&gt;，還可以自選字型、背景色等等，產出的圖可以說相當有質感。&lt;/p&gt;
&lt;p&gt;當傳送圖片比較快或排版比較好的場合（例如Line, Facebook..），或是在部落格和文件中想直接放漂亮圖片，以及&lt;strong&gt;需要示範給對方看可是又不想要對方直接複製貼上&lt;/strong&gt;的時候可謂是相當實用。&lt;/p&gt;
&lt;p&gt;先來一張成果圖鎮樓：



&lt;img
  src=&#34;https://image.igouist.net/jM07HAC.webp&#34;
  alt=&#34;&#34;width=&#34;1292&#34; height=&#34;998&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;Carbon 的操作相當直覺且簡單，進入 &lt;a href=&#34;https://carbon.now.sh/&#34;&gt;Carbon 的頁面&lt;/a&gt; 後，中央的程式碼區塊可以自由編輯，通常會直接將要做成圖片的程式碼貼於此處。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/RT4XIqZ.webp&#34;
  alt=&#34;&#34;width=&#34;1035&#34; height=&#34;836&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而程式碼區塊的上方的兩個下拉選單分別是&lt;strong&gt;程式碼區塊的配色主題&lt;/strong&gt;和&lt;strong&gt;程式語言&lt;/strong&gt;，主要的樣式（例如程式碼上色）會以這兩個為主。&lt;/p&gt;
&lt;p&gt;接著的調色盤則是&lt;strong&gt;背景色&lt;/strong&gt;，即程式碼區塊外的顏色，也支援透明度。像我要將圖片放在部落格時，就可以讓背景色和部落格一致，使中央的程式碼區塊更強烈。&lt;/p&gt;
&lt;p&gt;最後的齒輪則是&lt;strong&gt;設定，字型和陰影等都可以從裡面做調整&lt;/strong&gt;。不過選項挺多的，建議可以自己都調看看，像我個人就固定會把上方的狀態列關閉。&lt;/p&gt;
&lt;p&gt;而右側就是&lt;strong&gt;匯出&lt;/strong&gt;的部分了，有發推特（真的有人會這樣發？我很懷疑）以及下載的選項，下載可以直接點選，或是下拉式輸入檔名和變更圖片大小格式等等。&lt;/p&gt;
&lt;p&gt;這邊就直接拿上一篇的 &lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;Line Notify 範例&lt;/a&gt; 的程式碼來試試吧。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JvWVrjQ.webp&#34;
  alt=&#34;&#34;width=&#34;900&#34; height=&#34;634&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;而製作出來的效果就會像這樣子：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/jM07HAC.webp&#34;
  alt=&#34;&#34;width=&#34;1292&#34; height=&#34;998&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這工具對我個人來說挺喜歡的，有時候要傳程式碼給同學都會直接截圖，有了這個之後就可以傳得優雅一點（對我就是想叫你自己打）&lt;/p&gt;
&lt;p&gt;但還沒有用在部落格的打算，畢竟我身邊問過的人都表示：如果他們找到的網頁不給複製就會直接關掉。這… 我選擇妥協。&lt;/p&gt;
&lt;p&gt;那麼今天的工具推薦就到這邊，歡迎立馬用你的同學 or 同事來試試看，保證能促進友誼、增加互動呦！&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://free.com.tw/carbon/&#34;&gt;Carbon 將程式碼轉為美麗圖片，在社群分享更引人注目&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mnya.tw/cc/word/1088.html&#34;&gt;Carbon：將程式碼轉換成美觀的圖片&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;其他相似工具&#34;&gt;其他相似工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://free.com.tw/codeimg-io/&#34;&gt;Codeimg 程式碼轉圖片，可自訂視窗陰影效果更適合分享社群網站&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://free.com.tw/codezen/&#34;&gt;CodeZen 將程式碼轉為 JPG、PNG 圖片，上色後加入視窗及陰影效果&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>我要訂便當 (4): 將 Python 腳本部署上 Heroku</title>
      <link>https://igouist.github.io/post/2020/05/bandon-4-heroku/</link>
      <pubDate>Sun, 03 May 2020 23:55:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/05/bandon-4-heroku/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;2024.10 更新: Line Notify 將於 2025 年 3 月停止服務（&lt;a href=&#34;https://notify-bot.line.me/closing-announce&#34;&gt;LINE Notify 結束服務公告&lt;/a&gt;），有看到這篇的朋朋請選擇一組新的通知服務來串吧 QQ&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;目標：紀錄一下如何將 Python 寫好的東西丟上 &lt;a href=&#34;https://www.heroku.com/home&#34;&gt;Heroku&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在先前的 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;訂便當系列&lt;/a&gt; 中已經建立了一個替我們去爬便當網並通知我們的小工具，但還存在一個相當大的問題：要在哪跑？總不能就只放在家裡電腦有開機就跑、沒開機就算了吧？因此我們必須找個主機把它放上去。而在上一篇做 Line Notify 的時候所參考的 &lt;a href=&#34;https://www.evanlin.com/go-line-notify/&#34;&gt;如何快速建置一個 LINE Notify 的服務&lt;/a&gt; 中提到了將服務放上 Heroku 的部分，因此馬上嘗試看看。&lt;/p&gt;
&lt;p&gt;結果過程中碰了不少壁，因此特地紀錄下來。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Heroku 是一個雲端平台&lt;/strong&gt;，讓使用者可以把服務丟上去跑，&lt;del&gt;同時最讚的部分是，免費用戶就提供了每月 450 小時可以使用（但有每半小時會進入休眠的限制）&lt;/del&gt;，詳情可以參閱 &lt;a href=&#34;https://www.heroku.com/pricing&#34;&gt;Heroku 的計費頁面&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更新：Heroku 已經改為收費方案了 QQ&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;本系列最後一階段的目標就是要將訂便當爬蟲整套丟上去雲端上運行，達到被動接收通知的效果。但由於訂便當爬蟲還牽涉到用 Selenium 開瀏覽器互動，以及使用 Sqlite 儲存的部分，轉移到 Heroku 的環境上還需要進行一些調整。因此&lt;strong&gt;這一階段將會分成兩篇（或以上）文章進行，上篇會先簡單地紀錄如何將服務放上 Heroku，下篇開始則著重於紀錄將訂便當系統放上 Heroku 時遇到的問題和解決過程&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;本篇主要的參考來源來自於 &lt;a href=&#34;https://medium.com/enjoy-life-enjoy-coding/heroku-%E6%90%AD%E9%85%8D-git-%E5%9C%A8-heroku-%E4%B8%8A%E9%83%A8%E7%BD%B2%E7%B6%B2%E7%AB%99%E7%9A%84%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8-bf4fd6f998b8&#34;&gt;搭配 Git 在 Heroku 上部署網站的手把手教學&lt;/a&gt;、&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10196129&#34;&gt;發布網站到 Heroku&lt;/a&gt; 以及 &lt;a href=&#34;https://railsbook.tw/chapters/32-deployment-with-heroku.html&#34;&gt;網站部署（使用 Heroku）&lt;/a&gt; 這幾篇，其餘參考將附於文末，感謝各位前人大大留下的優質文。&lt;/p&gt;
&lt;h2 id=&#34;註冊及安裝&#34;&gt;註冊及安裝&lt;/h2&gt;
&lt;p&gt;Heroku 的 &lt;a href=&#34;https://signup.heroku.com/login&#34;&gt;註冊&lt;/a&gt; 相當簡單，這邊就略過不提，需要說明可以參考 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10196129&#34;&gt;這篇&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;註冊完畢就會回到個人頁面，底部有 Heroku Dev Center，也能選自己慣用的程式語言進去教學說明，有興趣的可以逛個一圈。



&lt;img
  src=&#34;https://image.igouist.net/2Q02pgx.webp&#34;
  alt=&#34;&#34;width=&#34;1260&#34; height=&#34;839&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊我們就先新增一個應用程式，點選畫面中間左側的 &lt;code&gt;Create a new app&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BhCYXCY.webp&#34;
  alt=&#34;&#34;width=&#34;706&#34; height=&#34;416&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著輸入應用程式名稱，注意只能使用小寫，並且不能重複。如果已經使用後續會提到的 CLI 的朋友，也可以直接使用 &lt;code&gt;heroku create&lt;/code&gt; 的指令來建立應用程式。&lt;/p&gt;
&lt;p&gt;建立完成之後就會進到專案的頁面：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OR5Sf14.webp&#34;
  alt=&#34;&#34;width=&#34;1284&#34; height=&#34;874&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見 Heroku 提供了幾種方法來將應用程式推送到 Heroku 上，例如連接到 Github。我們這邊用 Heroku CLI 試試，頁面上也已經提供了步驟說明。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://devcenter.heroku.com/articles/heroku-cli&#34;&gt;Heroku CLI 的安裝說明頁面&lt;/a&gt;，在這邊挑選自己的系統下載並安裝。&lt;/p&gt;
&lt;p&gt;安裝過程也相當簡易，會有將 Heroku 加到環境變數和路徑等選項，一路下一步即可。&lt;/p&gt;
&lt;p&gt;裝完之後，打開命令列輸入 Heroku，如果有跳出指令列表就代表安裝已經成功&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nGlmGiJ.webp&#34;
  alt=&#34;&#34;width=&#34;479&#34; height=&#34;323&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;連接至-heroku&#34;&gt;連接至 Heroku&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：由於 Heroku 傳送檔案的方式是使用 Git 進行，因此在後續的操作之前，&lt;strong&gt;必須先確保電腦中已經安裝 Git&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著我們就按照上面 Heroku CLI 說明頁面的步驟開始操作。&lt;/p&gt;
&lt;p&gt;首先要先登入，輸入 &lt;code&gt;heroku login&lt;/code&gt; 之後，便會開啟瀏覽器進行登入驗證，如果成功會看見登入訊息&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fOifmbs.webp&#34;
  alt=&#34;&#34;width=&#34;691&#34; height=&#34;188&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就可以移到專案的資料夾，將專案和 Heroku 做連接&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 先移動到專案的資料夾
$ cd my-project/

# Git 初始化
$ git init

# 和 Heroku 專案連接
$ heroku git:remote -a Heroku的APP名稱
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TumTYwP.webp&#34;
  alt=&#34;&#34;width=&#34;741&#34; height=&#34;196&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;環境設定及測試&#34;&gt;環境設定及測試&lt;/h2&gt;
&lt;p&gt;這邊由於前面提到訂便當系統有部分要調整的關係，因此只使用 &lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;我要訂便當(3) 的 Lint Notify 測試版&lt;/a&gt; 的 Line Notify 上去試試看。上一集的簡單 Line Notify 程式碼如下，這邊命名為 &lt;code&gt;myApp.py&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; requests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lineNotifyMessage&lt;/span&gt;(token, msg):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; token,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;application/x-www-form-urlencoded&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    payload &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;: msg}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;post(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://notify-api.line.me/api/notify&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        headers&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;headers,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        params&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status_code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Line Notify + Heroku 測試&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;token &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;YOUR TOKEN&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lineNotifyMessage(token, message)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著除了主要的 py 檔以外，還需要一些檔案去告訴 Heroku 怎麼處理我們的服務。&lt;/p&gt;
&lt;p&gt;事實上我第一次是直接就把 py 傳上去 Heroku 然後建置大失敗，還好 Google 了一陣有前人指點，這邊的設定部份參照自 &lt;a href=&#34;https://blog.techbridge.cc/2020/03/08/how-to-use-heroku-to-deploy-clear-mysql-db-web-app-tutorial/&#34;&gt;如何使用 Heroku 部屬一個 Web App 網頁應用程式&lt;/a&gt;，特此感謝。&lt;/p&gt;
&lt;p&gt;不過 Heroku 還不知道怎麼建我們的服務，因此這邊還需要新增幾個設定的檔案：&lt;/p&gt;
&lt;h3 id=&#34;procfile&#34;&gt;Procfile&lt;/h3&gt;
&lt;p&gt;用來告訴 Heroku 怎麼運行我們的服務&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;worker: python myApp.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;這行的意思是：&lt;code&gt;worker&lt;/code&gt; 的運行方式是 &lt;code&gt;python myApp.py&lt;/code&gt; 這行命令，&lt;strong&gt;Heroku 會根據這個文件的內容去建 dynos 容器來運行我們的應用服務&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;像上面的參考文章在建置 Flask 時，就將運行 &lt;code&gt;web&lt;/code&gt; 的命令設定為 &lt;code&gt;gunicorn flask_app:app&lt;/code&gt; 讓 &lt;a href=&#34;https://www.jianshu.com/p/52d8e3deaa16&#34;&gt;Gunicorn&lt;/a&gt; 去起網站來跑。&lt;/p&gt;
&lt;p&gt;由於這次的示範組只是個小腳本，因此我們宣告個 worker 而不是網站方便之後處理。當宣告的運行是 &lt;code&gt;web&lt;/code&gt; 時，Heroku 還會幫忙接 HTTP 的內容，當然想包裝成 Web APP 的朋友，也可以參考上面的文章將腳本包裝成簡單的 Flask。&lt;/p&gt;
&lt;p&gt;關於 Procfile 和 dynos，可以參見 &lt;a href=&#34;https://andyyou.github.io/2016/10/31/process-types-and-profile/&#34;&gt;Heroku 運行類別、 Procfile、常用指令筆記&lt;/a&gt; 以及 Heroku 官方的 &lt;a href=&#34;https://devcenter.heroku.com/categories/dynos&#34;&gt;Dynos (app containers)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這邊另外要注意的地方有兩個：這個檔案是不需要副檔名的，而且大小寫請正確。我在碰壁的過程中有遇到本機測試可以但推送上去就不行的情況，後來發現是打成全小寫了，改成字首大寫就正常，因此這邊提醒一下大家。&lt;/p&gt;
&lt;h3 id=&#34;requirementstxt&#34;&gt;requirements.txt&lt;/h3&gt;
&lt;p&gt;用來標示需要安裝的套件，逐行列出套件即可，也可以用 &lt;code&gt;套件名稱==版本號&lt;/code&gt; 的方式指定套件版本。由於測試的程式碼只用到 requests，因此這邊列上 requests 就可以了。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;requests
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;runtimetxt&#34;&gt;runtime.txt&lt;/h3&gt;
&lt;p&gt;用來標示 Python 的版本，這篇文當下的版本為&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;python-3.7.6
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;本機測試&#34;&gt;本機測試&lt;/h3&gt;
&lt;p&gt;到目前為止沒意外的話應該會包含這些東西



&lt;img
  src=&#34;https://image.igouist.net/cinuyWc.webp&#34;
  alt=&#34;&#34;width=&#34;394&#34; height=&#34;127&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊可以嘗試在本機測試看看，打開命令列輸入 &lt;code&gt;heroku local worker&lt;/code&gt; 就可以在 localhost Run 起來看看囉。注意這是在本機啟動 worker 這個 dynos 的意思，所以如果前面的 &lt;code&gt;Procfile&lt;/code&gt; 是使用 web 的朋友，這邊就要輸入 &lt;code&gt;heroku local web&lt;/code&gt; 囉。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7nfsEB0.webp&#34;
  alt=&#34;&#34;width=&#34;533&#34; height=&#34;96&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;




&lt;img
  src=&#34;https://image.igouist.net/UOfqN2J.webp&#34;
  alt=&#34;&#34;width=&#34;352&#34; height=&#34;119&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;想要全面啟動，可以直接輸入 &lt;code&gt;heroku local&lt;/code&gt;；想要指定 Port 的也可使用 &lt;code&gt;heroku local -p 7000&lt;/code&gt; 等等，可以參見 &lt;a href=&#34;https://andyyou.github.io/2016/10/31/process-types-and-profile/&#34;&gt;Heroku 運行類別、 Procfile、常用指令筆記&lt;/a&gt; 的啟動段落有比較常用的用法。&lt;/p&gt;
&lt;p&gt;如果一切安好，就可以開始嘗試部署囉&lt;/p&gt;
&lt;h2 id=&#34;部署至-heroku&#34;&gt;部署至 Heroku&lt;/h2&gt;
&lt;p&gt;Heroku 的部署只需要用 Git 推送上去就可以了，也就是只需要&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 把所有檔案都加到這次變更
$ git add .

# Commit 所有變動（記得標註解，養成好習慣）
$ git commit -am &amp;#34;這是Commit註解&amp;#34;

# 推送
$ git push heroku master
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;就可以發到 Heroku 進行部署了，推送時也能看見建置的過程，如安裝的包和建置是否成功等資訊都會顯示&lt;/p&gt;
&lt;p&gt;如果前面架上去的是網站，也就是 &lt;code&gt;Procfile&lt;/code&gt; 使用 Web 的朋友，Heroku 應該會幫忙把服務建起來。&lt;strong&gt;而像我是另外定義 &lt;code&gt;worker&lt;/code&gt; 的朋友，這邊還需要多一個把 dynos 建起來的動作&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;命令列中輸入 &lt;code&gt;heroku ps&lt;/code&gt; 就可以看到現在正在運行的服務，如果沒有任何服務運行，或是想擴展服務，可以使用 &lt;code&gt;heroku ps:scale Procfile裡定義的服務=服務數&lt;/code&gt; 來操作。&lt;/p&gt;
&lt;p&gt;例如我是使用 worker，這邊就輸入 &lt;code&gt;heroku ps:scale worker=1&lt;/code&gt; 就會開啟一個 worker 來運行；反過來說，輸入 &lt;code&gt;heroku ps:scale worker=0&lt;/code&gt; 就可以停止 worker 的服務囉。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/04TWfuB.webp&#34;
  alt=&#34;&#34;width=&#34;575&#34; height=&#34;120&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果這邊部屬和運行有問題的朋友，可以查看 Heroku 的 Log：在命令列輸入 &lt;code&gt;Heroku log&lt;/code&gt; 就會顯示了（建議搭配 &lt;code&gt;--tail&lt;/code&gt; 等方法使用，詳請可見 &lt;a href=&#34;https://devcenter.heroku.com/articles/logging#view-logs&#34;&gt;官方文檔&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;此外，若是跟我初嘗試一樣跑出 &lt;code&gt;Couldn&#39;t find that process type&lt;/code&gt; 請參照 &lt;a href=&#34;https://help.heroku.com/W23OAFGK/why-am-i-seeing-couldn-t-find-that-process-type-when-trying-to-scale-dynos&#34;&gt;這篇&lt;/a&gt;，我個人是修改 &lt;code&gt;Procfile&lt;/code&gt; 檔名的大小寫就解決了&lt;/p&gt;
&lt;p&gt;若是建置時無法成功的朋友，可能是沒抓到你的服務的語言（例如 Python），需要自己去拉一下 buildpacks，例如 Python 的建置包就是 &lt;a href=&#34;https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-python&#34;&gt;heroku-buildpack-python&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;最後我們回來看一下 Heroku 的 APP 頁面&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9aJyKLQ.webp&#34;
  alt=&#34;&#34;width=&#34;1281&#34; height=&#34;451&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Activity&lt;/code&gt; 可以看見最近的活動，例如部署失敗也會顯示在這；而 &lt;code&gt;Settings&lt;/code&gt; 則是一些專案設定，例如名稱和建置包都要到這裡調整&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/p9ZAWei.webp&#34;
  alt=&#34;&#34;width=&#34;1241&#34; height=&#34;282&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其中可以從 &lt;code&gt;Domains&lt;/code&gt; 這裡連到你的服務，如果你放的是網頁或 API 的話就可以從這裡進入。不過更快進入自己服務的方法，是在命令列中直接下 &lt;code&gt;Heroku open&lt;/code&gt; 就行啦。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XhqJvxH.webp&#34;
  alt=&#34;&#34;width=&#34;780&#34; height=&#34;67&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;後記&#34;&gt;後記&lt;/h2&gt;
&lt;p&gt;其實原本打算部署的部分一篇解決的，沒想到牆就這樣撞了上來，將便當放到 Heroku 上時實在遇到挺多問題，例如第一次使用時直接把整坨檔案直接推上去也沒設定結果建置失敗，或是發現 Heroku 不給用 Sqlite，或是 Selenium 要另外找建置包等等，因此最後決定切成兩篇，一篇好好記 Heroku 的流程，剩下的部份再按照問題做紀錄。&lt;/p&gt;
&lt;p&gt;同時因為原本的訂便當已經在家裡電腦跑著了，部屬上雲似乎是額外再戰的部份。待到剩下問題解決了，抓緊下班時間再寫上來記著，希望以後能派上用場吧。&lt;/p&gt;
&lt;h2 id=&#34;我要訂便當系列&#34;&gt;我要訂便當系列&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;我要訂便當(2) —— 用 Python + Sqlite 儲存訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;我要訂便當(3) —— 用 Python + Line Notify 傳送通知&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;我要訂便當(4) —— 將 Python 腳本部署上 Heroku&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-5-heroku-debug/&#34;&gt;我要訂便當(5) —— Heroku 填坑小記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/enjoy-life-enjoy-coding/heroku-%E6%90%AD%E9%85%8D-git-%E5%9C%A8-heroku-%E4%B8%8A%E9%83%A8%E7%BD%B2%E7%B6%B2%E7%AB%99%E7%9A%84%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8-bf4fd6f998b8&#34;&gt;搭配 Git 在 Heroku 上部署網站的手把手教學 - 神Q超人&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10196129&#34;&gt;第十八天：發布網站到 Heroku - 只要有心，人人都可以做卡米狗&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://railsbook.tw/chapters/32-deployment-with-heroku.html&#34;&gt;網站部署（使用 Heroku） - 為你自己學 Ruby on Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.techbridge.cc/2020/03/08/how-to-use-heroku-to-deploy-clear-mysql-db-web-app-tutorial/&#34;&gt;如何使用 Heroku 部屬一個 Web App 網頁應用程式 - TechBridge 技術共筆部落格&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.evanlin.com/go-line-notify/&#34;&gt;[Golang][LINE][教學] 如何快速建置一個 LINE Notify 的服務 - KKDAI.GITHUB.IO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ivanjo39191.pixnet.net/blog/post/179102363-python-django-%E5%AD%B8%E7%BF%92%E7%B4%80%E9%8C%84%28%E4%B9%9D%29-%E9%83%A8%E5%B1%AC%E7%B6%B2%E7%AB%99%E5%88%B0-heroku&#34;&gt;Python Django 學習紀錄(九) 部屬網站到 Heroku - IvanKao的部落格&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://djangogirlstaipei.herokuapp.com/tutorials/deploy-to-heroku/?os=windows&#34;&gt;用 Heroku 部署網站 - Django Girls Taipei&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://andyyou.github.io/2016/10/31/process-types-and-profile/&#34;&gt;Heroku 運行類別、 Procfile、常用指令筆記&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.longwin.com.tw/2019/03/python-pip-requirements-txt-management-package-2019/&#34;&gt;Python PIP 使用 requirements.txt 管理套件相依性&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-python&#34;&gt;Heroku-buildpack-python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://devcenter.heroku.com/categories/heroku-architecture&#34;&gt;Heroku Dev Center&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://devcenter.heroku.com/articles/getting-started-with-python&#34;&gt;Heroku Dev Center: Getting Started on Heroku with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/michaelkrukov/heroku-python-script&#34;&gt;Template for hosting python scripts and applications on Heroku - Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>CSS: 變數 (Variables)</title>
      <link>https://igouist.github.io/post/2020/04/css-variables/</link>
      <pubDate>Sun, 26 Apr 2020 23:09:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/04/css-variables/</guid>
      <description>&lt;p&gt;這禮拜忙了些，只好先偷懶打張安牌。這邊就記一下前陣子同學弄主題切換時用到的 CSS 變數（Variables）功能用法&lt;/p&gt;
&lt;p&gt;原本使用 CSS 時就會有許多重複使用的部分，例如說網站的主色彩和副色彩等，然而在管理上，或是要修改的時候就會很麻煩，通常都要另外借助工具來處理。然而 CSS 其實原生就有變數可以使用，大大地增加了改動時的方便性。用法上也相當簡單。&lt;/p&gt;
&lt;p&gt;在 CSS 宣告變數時，建議放在 &lt;code&gt;:root&lt;/code&gt; 裡，並使用 &lt;code&gt;--變數名&lt;/code&gt; 的方式宣告。例如&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;:&lt;span style=&#34;color:#a6e22e&#34;&gt;root&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    --color: &lt;span style=&#34;color:#ae81ff&#34;&gt;#000000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而使用的時候只要 &lt;code&gt;var(--變數名)&lt;/code&gt; 就可以囉。例如&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;body&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;平時使用也常搭配 CSS 的 clac 及 JS 的 document.documentElement.style.setProperty 來處理，這部分就直接看範例吧。
以下附上調色盤 Codepen 作為範例，用法主要參考 &lt;a href=&#34;https://pjchender.dev/js30/js30-day03/&#34;&gt;CSS Variables&lt;/a&gt; 這篇。&lt;/p&gt;
&lt;p class=&#34;codepen&#34; data-height=&#34;265&#34; data-theme-id=&#34;dark&#34; data-default-tab=&#34;css,result&#34; data-user=&#34;igouist&#34; data-slug-hash=&#34;NWPBMwP&#34; style=&#34;height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&#34; data-pen-title=&#34;CSS 變數測試：調色盤&#34;&gt;
  &lt;span&gt;See the Pen &lt;a href=&#34;https://codepen.io/igouist/pen/NWPBMwP&#34;&gt;
  CSS 變數測試：調色盤&lt;/a&gt; by IGOU (&lt;a href=&#34;https://codepen.io/igouist&#34;&gt;@igouist&lt;/a&gt;)
  on &lt;a href=&#34;https://codepen.io&#34;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;script async src=&#34;https://static.codepen.io/assets/embed/ei.js&#34;&gt;&lt;/script&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pjchender.dev/js30/js30-day03/&#34;&gt;CSS Variables - PJCHENder私房菜&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://muki.tw/tech/native-css-variables/&#34;&gt;SASS, LESS 退散，原生 CSS 可以使用變數啦！ - MUKI space*&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mnya.tw/cc/word/1340.html&#34;&gt;CSS 原生變數（Variables）介紹與使用教學 - 萌芽綜合天地&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://w3c.hexschool.com/blog/21985acb&#34;&gt;原生 CSS 變數運用技巧（CSS Variables）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Feedly —— 用 RSS 訂閱來主動篩選資訊吧</title>
      <link>https://igouist.github.io/post/2020/04/feedly/</link>
      <pubDate>Sun, 19 Apr 2020 23:56:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/04/feedly/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;「你看，我認為人的腦子本來就像一間空空的小閣樓，應該有選擇地把傢俱放進去，傻瓜才會把他見到的所有破爛一古腦兒的裝進去。這樣一來，那些對他有用的知識反而被擠了出來；或者，最多不過是和許多其他的東西摻雜在一起，在取用的時候也會很困難。所以一個會工作的人，在要把一些東西裝進他那間小閣樓似的頭腦中的時候，確實是非常小心謹慎的。」&lt;/p&gt;
&lt;p&gt;　　——《福爾摩斯探案記：血字的研究》&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在咱們寫程式這行中，幾乎所有人都必須要在網路上找尋資料；而就算非這一行的朋友們，也會在網路上多方攝取資源。隨著時間的累積，都會開始有一份列表，諸如一些崇拜的大神或是固定追蹤的部落格，又或是文風比較喜歡、素質感覺比較高的新聞或評論等等。&lt;/p&gt;
&lt;p&gt;然而列表越來越長，就算加到書籤也總不能沒事就逛一大圈，也常常逛到不知道到底在幹嘛囧。因此把多個資訊來源集中起來整理就成了面對資訊爆炸的關鍵一步。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://feedly.com/&#34;&gt;Feedly&lt;/a&gt; 就是一個協助資訊來源管理的工具，可以很方便地把不同訂閱來源整理在一起&lt;/strong&gt;，例如你可能追隨了五六個大神的部落格，只要這些大神都有提供 RSS 訂閱，Feedly 就會將大神更新的文章收集到平台上。&lt;/p&gt;
&lt;p&gt;每天只需要上去 Feedly 看一下有沒有哪位大神更新，就可以化主動為被動，穩定接收新資訊。並且由於集中管理的關係，也可以檢視是否有哪個資料來源，如新聞網站，實際上更新的文章並不太合你胃口，或是哪些已經停止更新了，就可以進行剪枝的動作，建立自己的篩選機制。&lt;/p&gt;
&lt;p&gt;Feedly 使用的是 RSS，是一種自古以來就存在(?)的訂閱方式。主要是&lt;strong&gt;將網站裡文章的標題和簡介等資訊整理成 XML 的文字格式&lt;/strong&gt;（例如本站的&lt;a href=&#34;https://igouist.github.io/index.xml&#34;&gt;RSS頁面&lt;/a&gt;）&lt;strong&gt;，使訂閱服務只需要去各個網站抓取輕便的文字檔就能夠得知網站是否更新、現在有哪些文章等資訊&lt;/strong&gt;。因此本身可以說是相當輕量簡單的服務。&lt;/p&gt;
&lt;p&gt;而我們就可以用 Feedly 定期去取得我們列好的 RSS 資訊，省下我們在網站間奔波的時間。類似的服務還有 Inoreader 等等，本質上並沒有太大的差別，可以挑喜歡的試用看看。&lt;/p&gt;
&lt;h2 id=&#34;開始使用如何訂閱&#34;&gt;開始使用＋如何訂閱&lt;/h2&gt;
&lt;p&gt;到 &lt;a href=&#34;https://feedly.com/&#34;&gt;Feedly&lt;/a&gt; 的首頁進行註冊之後，會先導到添加資訊來源的頁面如下（我的頁面是黑色的，是因為左側選單有黑暗模式可以開，點一下月亮就可以囉）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XbpJvdz.webp&#34;
  alt=&#34;&#34;width=&#34;1575&#34; height=&#34;759&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;搜尋框裡面可以輸入 文字、網址和 RSS 網址 來進行搜尋，以本網站為例，貼上網址之後，Feedly 就會去找是否提供 RSS，有的話就會像這樣搜尋到，便可以按下 Follow；可以從圖片中看見現在只有我一個人追蹤我自己（泣）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/S71J7dM.webp&#34;
  alt=&#34;&#34;width=&#34;728&#34; height=&#34;560&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;按下追隨之後，可以按 + New Feed，&lt;strong&gt;這個 Feed 可以把它看作是一種分類&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WUrdijS.webp&#34;
  alt=&#34;&#34;width=&#34;332&#34; height=&#34;205&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;由於本站大多分享程式相關的部分，因此我們這邊建立程式設計的分類&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qxPHVg0.webp&#34;
  alt=&#34;&#34;width=&#34;327&#34; height=&#34;226&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;加完之後，就可以在左側看見剛剛建好的分類已經來源囉，分類及來源旁邊的數字則是這個分類下的新文章的篇數&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DIftHjN.webp&#34;
  alt=&#34;&#34;width=&#34;388&#34; height=&#34;327&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;為了之後說明方便，再多加幾個來源。下面是我一個挺喜歡的部落格&lt;a href=&#34;https://www.playpcesor.com/&#34;&gt;電腦玩物&lt;/a&gt;，分享挺多時間管理和生活流程等等的工具和技巧。像這種大神級只需要輸入名字搜尋就可以追蹤囉&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/29VwdC6.webp&#34;
  alt=&#34;&#34;width=&#34;933&#34; height=&#34;606&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在這一步就可以把平常會看的技術部落格、週刊之類的都加進來，並且予以分類。&lt;/p&gt;
&lt;p&gt;往後&lt;strong&gt;如果需要訂閱新的來源，在左側選單的 &lt;code&gt;＋&lt;/code&gt; 就可以再回到增加來源的頁面囉！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;基本操作&#34;&gt;基本操作&lt;/h2&gt;
&lt;p&gt;訂閱了一些來源之後，就可以嘗試使用。我個人每天會來到 Feedly 從 Today 的部分挑選幾篇標題讓人感興趣的文章來看。可以從左上角點選 Today 就可以回到訂閱文的頁面。&lt;/p&gt;
&lt;p&gt;右上角可以選擇文章排列的模式，例如很像信箱的顯示標題，和我較喜歡的標題內文，另外也還有像卡片格狀排列的模式可以選，建議先選個喜歡的版面會提高每天閱讀的意願。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GBsnQU3.webp&#34;
  alt=&#34;&#34;width=&#34;904&#34; height=&#34;689&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;因此&lt;strong&gt;最主要的活動範圍就會在這個 Today 進行&lt;/strong&gt;，邊滑動邊選擇不同來源的文章來閱讀，也可以從左側選擇某個分類、甚至某個來源的文章來看。若是網站可以擷取的，&lt;strong&gt;點選文章之後就會從右側彈出文章的簡介，全文仍然要回到來源網頁閱讀&lt;/strong&gt;，為了排版舒適以及支持寫手的流量來看這是相當好的做法。故打開文章之後往下拉，可以看見 &lt;strong&gt;VISIT WEBSITE&lt;/strong&gt; 按下去就會到目標文章囉&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ER63IbJ.webp&#34;
  alt=&#34;&#34;width=&#34;751&#34; height=&#34;680&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;此外也可以看見上面一排工具列，&lt;strong&gt;若是有購買付費服務，就可以和其他像是 Evernote 的工具連動，把文章傳送過去&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而上方工具列最左側的&lt;strong&gt;書籤圖案則是稍後閱讀 Read Later&lt;/strong&gt;，在這邊選取之後或是在 Today 文章列表有選取的話，&lt;strong&gt;就會記錄到左側選單的 Read Later&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7GVCm8l.webp&#34;
  alt=&#34;&#34;width=&#34;743&#34; height=&#34;232&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接下來最重要的就是已讀功能，除了閱讀完單篇文章標示為已讀，以及在列表對某邊文章打勾以外，當 Today 或某個分類捲動到底時，會有一個 全部標示為已讀 的按鈕可以使用。或是上圖的右側也可以看到一個標著數字的打勾，也是全部已讀的意思。已讀之後就會收到過往已讀的清單中。靠著分類和已讀就可以將每天的資訊整個流動過去。&lt;/p&gt;
&lt;p&gt;此外，如果跟我一樣&lt;strong&gt;一開始就加上一堆來源的，文章的數量就會爆炸多，左邊的數字也會超級大&lt;/strong&gt;。但這並不用擔心，在資訊爆炸的年代，資訊本身就是看不完的，有些人會因此感到焦慮，但其實篩選比起暴飲暴食更為重要，因此我個人是建議&lt;strong&gt;排定文章分類的優先順序，並且懂得取捨&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我個人現在是分成 程式設計、知識、科技新聞、一般新聞 等類別，這也代表我個人對這些資訊的優先順序；時間少的時候就從優先順序高的開始看，例如技術大神的新文章，並直接捨棄新聞等較不重要的部分；時間充裕的時候才考慮大致瀏覽過一次去挑想看的來看。&lt;strong&gt;知識類的東西，看了也許有賺，但如果沒有時間看了，不看也不虧，因此並不要太過執著，必要的時候直接全部已讀也是合適的做法&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;來源管理&#34;&gt;來源管理&lt;/h2&gt;
&lt;p&gt;如果只是訂閱各網站集中起來，那麼和月刊和報紙等也沒什麼太大的差異。Feedly 最吸引我的一點就是對來源管理的方便度。在每個分類右上角的選單會有個管理來源&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lXgrTi2.webp&#34;
  alt=&#34;&#34;width=&#34;492&#34; height=&#34;464&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;或是左下角的頭像中也可以進入&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/HKnJ8pH.webp&#34;
  alt=&#34;&#34;width=&#34;343&#34; height=&#34;302&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;進入之後就會到資源管理的頁面&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tB4w9J9.webp&#34;
  alt=&#34;&#34;width=&#34;899&#34; height=&#34;752&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;上面會告訴你訂閱了幾個來源，有哪些來源已經有一段時間沒更新（有睡了跟死了兩個程度），&lt;strong&gt;每個來源也可以確認每個月的貼文數量，以及實際上每個月有點進去閱讀的數量&lt;/strong&gt;。藉此就可以掌握那些媒體可能過於嘈雜，或是哪些來源的文章其實並沒有興趣等等，可以按照自己的需求去管理資訊的來源。經過不斷的篩選和添加，就能夠讓整個頁面成為你的形狀。&lt;/p&gt;
&lt;h2 id=&#34;心得&#34;&gt;心得&lt;/h2&gt;
&lt;p&gt;這幾天和女友及同事聊到都逛哪些部落格之類的話題時，發現大多都還是有空的時候主動去巡查一遍，最多就到信箱訂閱或是 FB 按讚然後佛系看到這樣，讓我覺得應該推廣一下 Feedly 這股神器。雖然這篇和技術沒什麼直接關係，幸好六角的鼠年全馬有八篇心得文的扣打，就直接選擇用在這裡。&lt;/p&gt;
&lt;p&gt;有看過前幾篇文章的應該能夠了解，對我而言&lt;strong&gt;比起主動去做這些事情，不如建立一個自動化的機制被動去接收訊息，省去過程這段不必要的時間&lt;/strong&gt;；例如比起常常上去便當網或 PTT 找我要的東西出現了沒，我更喜歡讓電腦替我代勞並在出現之後提醒我的做法。因此想看的文章想追的網站多了，Feedly 對我來說就是一件神兵利器，節省了許多功夫。&lt;/p&gt;
&lt;p&gt;我曾聽過一句話：資訊不等於知識。我相信資訊的來源是需要主動去選擇的，這也就是開頭引用福爾摩斯的這句名言的原因。若是&lt;strong&gt;不管什麼東西都直接吃下肚，甚至不管多少都堅持吃完，那只會得到無邊的痛苦&lt;/strong&gt;。經過有系統的整理和篩選，最終才能建立方便又有效的流程。&lt;/p&gt;
&lt;p&gt;然而各大社群平台的演算法實在太謎，大多部落格也不會有月刊或信箱發送，因此還是要主動出擊去做篩選，不斷檢視過程和品質，留下自己想看並且素質值得期待的資訊來源，才能建立高品質的資訊閱讀，看得健康又有用。就像電腦玩物在 Feedly 的介紹文中說的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「只有在自己為自己訂閱、整理資訊管道的過程中，你才能逐漸建立起自己的知識世界觀，開始思考自己需要什麼資訊，而這時候的資訊閱讀才是有效的。」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;共勉之。&lt;/p&gt;
&lt;h2 id=&#34;延伸閱讀參考資料&#34;&gt;延伸閱讀、參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2018/04/rss-facebook.html&#34;&gt;RSS 不是臉書演算法解藥，我們真正需要的是垂直閱讀與實踐&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.bnext.com.tw/article/48743/can-rss-revival&#34;&gt;被演算法「毀掉」的閱讀習慣，用RSS救得回來嗎？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2017/08/feedly.html&#34;&gt;Feedly 新功能找回高品質資訊閱讀，自動過濾無效與噪音網站&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.playpcesor.com/2013/03/feedly-google-reader.html&#34;&gt;Feedly 完全上手教學，延續 Google Reader 閱讀器體驗 - 電腦玩物&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://iamfugue.net/news-aggregator/&#34;&gt;【工具】RSS 訂閱初體驗 - 微 GEEK 百科&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.thelaziest.net/2019/12/rss-feedly.html&#34;&gt;免費 RSS 閱讀器 Feedly ，選擇真正需要的資訊，不再被演算法餵食垃圾內容&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>EPPlus —— 輕鬆處理 Excel</title>
      <link>https://igouist.github.io/post/2020/04/epplus/</link>
      <pubDate>Sun, 12 Apr 2020 10:19:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/04/epplus/</guid>
      <description>&lt;p&gt;前陣子工作需要匯出一些資料表，因此用到了 &lt;a href=&#34;https://github.com/EPPlusSoftware/EPPlus&#34;&gt;EPPlus&lt;/a&gt; 這套工具來把資料匯出成 Excel。由於這需求似乎會挺常遇到的，決定記一下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目標：稍微紀錄一下這次碰到 EPPlus 的用法。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;EPPlus 是在 .NET Framework 或 .NET Core 上提供控制 Excel 的元件，操作簡單好懂&lt;/strong&gt;，當有需要在網頁上讓人將表格資料下載成 Excel 的功能時就可以試著使用。常常一併被提起的還有另一個一樣老牌的相似功能元件 NPOI，不過我個人只用過 EPPlus 便不再贅述。關於更詳細的介紹，可以參閱黑暗執行緒的這篇 &lt;a href=&#34;https://blog.darkthread.net/blog/epplus/&#34;&gt;比 NPOI 更討喜的 Excel 元件 - EPPlus!&lt;/a&gt;，儘管是有些久遠的文章但仍能迅速了解 EEplus 的賣點和差異。&lt;/p&gt;
&lt;p&gt;我在使用 EPPlus 時主要的參考來自於 &lt;a href=&#34;https://github.com/EPPlusSoftware/EPPlus/wiki&#34;&gt;EPPlus 的 Wiki&lt;/a&gt;，每個功能都有說明及範例，同時也有範例專案可以下載，寫得相當詳細。另外還找了如 &lt;a href=&#34;https://www.cnblogs.com/rumeng/p/3785748.html&#34;&gt;導出 Excel 之 Epplus 使用教程 - Wico&amp;rsquo;s Blog&lt;/a&gt;、&lt;a href=&#34;https://dotblogs.com.tw/malonestudyrecord/2018/03/21/103124&#34;&gt;使用 EPPLUS 操控 Excel - 碼農的學習日誌&lt;/a&gt; 這些有逐步說明的文章，在此感謝。&lt;/p&gt;
&lt;h2 id=&#34;建立-excel&#34;&gt;建立 Excel&lt;/h2&gt;
&lt;p&gt;首先從建立一個 Excel 開始；要注意的是開啟檔案之後也要記得建分頁出來。後續的寫入資料等都是對分頁去做動作。&lt;/p&gt;
&lt;p&gt;我們使用 &lt;code&gt;new ExcelPackage()&lt;/code&gt; 來開一個新的 Excel 的處理工作，而結束之後一定要記得 &lt;code&gt;SaveAs&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註 (2020/4/12):&lt;/p&gt;
&lt;p&gt;由於 EPPlus 升版和授權上的一些改變，如果直接使用會跳出 &lt;code&gt;LicenseException&lt;/code&gt; 用來通知你這件事。&lt;/p&gt;
&lt;p&gt;因此現在需要先加上這行：&lt;br/&gt;
&lt;code&gt;ExcelPackage.LicenseContext = LicenseContext.NonCommercial;&lt;/code&gt; &lt;br/&gt;
來叫它閉嘴。&lt;/p&gt;
&lt;p&gt;另外也有在 &lt;code&gt;App.config&lt;/code&gt; 中設定的作法，可參閱 &lt;a href=&#34;https://epplussoftware.com/developers/licenseexception&#34;&gt;LicenseException - EPPlus&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;備註 (2022/3/13):&lt;/p&gt;
&lt;p&gt;因緣際會發現 EPPlus 的授權說明網頁現在已經有翻譯了，有遇到這個 LicenseException 的朋友可以去看一下：&lt;a href=&#34;https://epplussoftware.com/zh/Developers/LicenseException&#34;&gt;LicenseException - EPPlus Software&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;另外關於一些授權的部分，也可以參照小朱大大的這篇：&lt;a href=&#34;https://dotblogs.com.tw/regionbbs/2018/09/23/light-discussions-oss-licenses&#34;&gt;淺談軟體開源的授權條款&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ExcelPackage.LicenseContext = LicenseContext.NonCommercial; &lt;span style=&#34;color:#75715e&#34;&gt;// 關閉新許可模式通知&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 沒設置的話會跳出 Please set the excelpackage.licensecontext property&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; file = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;D:\ExampleExcel.xlsx&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 檔案路徑&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; excel = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ExcelPackage())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 建立分頁&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ws = excel.Workbook.Worksheets.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MySheet&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 寫入資料試試&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ws.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;].Value = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;測試測試&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 儲存 Excel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    excel.SaveAs(file);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;執行之後就可以看到檔案已經被建立囉



&lt;img
  src=&#34;https://image.igouist.net/gHEE7OZ.webp&#34;
  alt=&#34;&#34;width=&#34;191&#34; height=&#34;100&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且也出現了測試用的內容



&lt;img
  src=&#34;https://image.igouist.net/LObC4zj.webp&#34;
  alt=&#34;&#34;width=&#34;347&#34; height=&#34;273&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;從這邊也可以認出 &lt;code&gt;ws.Cells[2, 1].Value&lt;/code&gt; 的第一個數字是&lt;code&gt;從 ↓ 數下來&lt;/code&gt;的，第二個是&lt;code&gt;往 → 數過去&lt;/code&gt;的（很抱歉這樣表示，因為我常弄反行跟列，這樣之後參考比較好理解）&lt;/p&gt;
&lt;p&gt;另外眼尖的朋友也可以察覺到一個重點，&lt;strong&gt;儲存格是從 1 開始數的：不是 0 ！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也就是說 &lt;code&gt;A1&lt;/code&gt; 這一格是 &lt;code&gt;[1, 1]&lt;/code&gt;，不要打成 &lt;code&gt;[0, 0]&lt;/code&gt; 了。當然，對於困擾的朋友，EEPlus 也提供了 &lt;code&gt;ws.Cells[&amp;quot;B1&amp;quot;]&lt;/code&gt; 的寫法，可以用字串傳入的方式直接指定在 Excel 的儲存格位置，比起數格子方便多了，後續也會在示範標註。&lt;/p&gt;
&lt;h2 id=&#34;編輯-excel&#34;&gt;編輯 Excel&lt;/h2&gt;
&lt;p&gt;編輯時和建立一樣，從 &lt;code&gt;ExcelPackage()&lt;/code&gt; 開始動作，這次我們將檔案直接傳入，如此 Save 的時候就可以不用再傳一次。關於各種操作直接在程式碼上的註解進行說明。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 特別注意，頁籤和儲存格等操作 是由 1 開始而非 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 打開存在的 Excel 檔案&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; excelFile = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;@&amp;#34;D:\ExampleExcel.xlsx&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; excel = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ExcelPackage(excelFile))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 指定頁籤&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//ExcelWorksheet sheet1 = excel.Workbook.Worksheets[1]; // 這邊用是 1 在 Core 用是 0 = =&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ExcelWorksheet sheet1 = excel.Workbook.Worksheets[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MySheet&amp;#34;&lt;/span&gt;]; &lt;span style=&#34;color:#75715e&#34;&gt;// 可以使用頁籤名稱&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#region&lt;/span&gt; -- &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;儲存格讀寫&lt;/span&gt; --
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 寫入資料，[行－，列｜] 或直接指定 [&amp;#34;儲存格&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sheet1.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;].Value = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;開啟測試&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 嚴謹一點可以用 GetValue 和 SetValue 來操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//sheet1.Cells[&amp;#34;B1&amp;#34;].Value = &amp;#34;開啟測試&amp;#34;; // 此兩行等價&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sheet1.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;].Value = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;多格操作測試&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 從 (3, 3) 一路框到 (5, 5)，包含頭尾&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//sheet1.Cells[&amp;#34;C3:E5&amp;#34;].Value = &amp;#34;多格操作測試&amp;#34;; // 此兩行等價&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sheet1.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;].LoadFromText(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LoadFromText Test&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 從字串讀入資料，可用於寫入 CSV 之類的場合&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; coll = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&amp;gt; { &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LoadFromCollTest1&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LoadFromCollTest2&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LoadFromCollTest3&amp;#34;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sheet1.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;].LoadFromCollection(coll); &lt;span style=&#34;color:#75715e&#34;&gt;// 從集合類型的參數讀入資料，會按照行（＝ D1 E1 F1...）依序排列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 可從 LoadFromCollection 推測 LoadFromDataReader, LoadFromDataTable, LoadFromArrays 等函式的行為，故省略&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#endregion&lt;/span&gt; -- &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;儲存格讀寫&lt;/span&gt; --
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#region&lt;/span&gt; -- &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;儲存格樣式&lt;/span&gt; --
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 可以用宣告的方式一併操作指定區域內的儲存格&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; range = sheet1.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;])  &lt;span style=&#34;color:#75715e&#34;&gt;// 直接選取 A1 到 A5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        range.Value = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;樣式測試&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        range.Style.Font.Bold = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 粗體&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        range.Style.Font.Color.SetColor(Color.White); &lt;span style=&#34;color:#75715e&#34;&gt;// 字體顏色&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        range.Style.Fill.PatternType = ExcelFillStyle.Solid; &lt;span style=&#34;color:#75715e&#34;&gt;// 設定背景填色方法，沒有這一行就上背景色會報錯&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                             &lt;span style=&#34;color:#75715e&#34;&gt;// Solid = 填滿；另外還有斜線、交叉線、條紋等&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        range.Style.Fill.BackgroundColor.SetColor(Color.DarkBlue); &lt;span style=&#34;color:#75715e&#34;&gt;// 儲存格顏色&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#endregion&lt;/span&gt; -- &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;儲存格樣式&lt;/span&gt; --
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    excel.Save(); &lt;span style=&#34;color:#75715e&#34;&gt;// 儲存變更&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;執行完的結果會像這樣，可以和上面的程式碼對照看是哪一部份的效果&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/dENLdol.webp&#34;
  alt=&#34;&#34;width=&#34;473&#34; height=&#34;381&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;以上部份嘗試了 資料的讀寫（如寫入單格的資料內容、寫入一個串列等）以及樣式的設定（字體顏色、儲存格顏色等）等。由於其中的操作能&lt;strong&gt;選擇的樣式相當繁多&lt;/strong&gt;，例如背景填色就有好幾種，因此列出來並不是明智的做法，先知道基本的語法之後再自己開編譯器看有哪些選項、或是將對應的操作拿到官方文檔找看有哪些會是比較實際的做法。&lt;/p&gt;
&lt;p&gt;這篇只示範資料的寫入，當然 Excel 的處理沒有那麼簡單，可以參照 官方Wiki 的這兩個部份：&lt;a href=&#34;https://github.com/EPPlusSoftware/EPPlus/wiki/Formula-Calculation&#34;&gt;公式的計算&lt;/a&gt; 及 &lt;a href=&#34;https://github.com/EPPlusSoftware/EPPlus/wiki/Shapes,-Pictures,-Controls-and-Charts&#34;&gt;圖表的處理&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Epplus 的官方文檔範例寫得挺不錯的，有更進一步需求的朋友可以翻一下範例，抓下來跑跑看，可以解決大部分的問題。&lt;/p&gt;
&lt;h2 id=&#34;泛型串列匯出-excel&#34;&gt;泛型串列匯出 Excel&lt;/h2&gt;
&lt;p&gt;這邊紀錄一下工作需求時用到的做法，由於匯出資料的型別繁多，唯一的共通點就是都很多筆。為了方便寫了一個針對多筆的泛型匯出函式，特別在這裡記錄下來。
其實這篇也是為了把這個部份記錄下來方便以後可以回來抄才開的坑…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Main()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; data = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;testClass&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; testClass{ name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;香蕉&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; testClass{ name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;番茄&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; testClass{ name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;蘋果&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; testClass{ name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;鳳梨&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; excel = ExportExcel(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	excel.Dump();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; FileInfo ExportExcel&amp;lt;T&amp;gt;(IEnumerable&amp;lt;T&amp;gt; data) &lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt; T: &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;//var output = new MemoryStream();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; output = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; FileInfo(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;D:\\ExportExcelTest-&amp;#34;&lt;/span&gt; + DateTime.Now.ToString(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yyyy-MM-dd-hh-mm-ss&amp;#34;&lt;/span&gt;) + &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.xlsx&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; excel = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ExcelPackage(output))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; ws = excel.Workbook.Worksheets.Add(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Sheet1&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 建立分頁&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// 用反射拿出有 DisplayName 的屬性&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; properties = &lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt;(T)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .GetProperties()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .Where(prop =&amp;gt; prop.IsDefined(&lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt;(DisplayNameAttribute)));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rows = data.Count() + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;   &lt;span style=&#34;color:#75715e&#34;&gt;// 直：資料筆數（記得加標題列）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; cols = properties.Count(); &lt;span style=&#34;color:#75715e&#34;&gt;// 橫：類別中有別名的屬性數量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(rows &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &amp;amp;&amp;amp; cols &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			ws.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;].LoadFromCollection(data, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 寫入資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;// 儲存格格式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; colNumber = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;foreach&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; prop &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; properties)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// 時間處理，如果沒指定儲存格格式會變成 通用格式，就會以 int＝時間戳 的方式顯示&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (prop.PropertyType.Equals(&lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt;(DateTime)) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				   prop.PropertyType.Equals(&lt;span style=&#34;color:#66d9ef&#34;&gt;typeof&lt;/span&gt;(DateTime?)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					ws.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, colNumber, rows, colNumber].Style.Numberformat.Format = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mm-dd-yy hh:mm:ss&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				colNumber += &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;// 樣式準備&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; range = ws.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, rows, cols])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				ws.Cells.Style.Font.Name = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;新細明體&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				ws.Cells.Style.Font.Size = &lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				ws.Cells.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; &lt;span style=&#34;color:#75715e&#34;&gt;// 置中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				ws.Cells.AutoFitColumns(); &lt;span style=&#34;color:#75715e&#34;&gt;// 欄寬&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// 框線&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				range.Style.Border.Top.Style = ExcelBorderStyle.Thin;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				range.Style.Border.Left.Style = ExcelBorderStyle.Thin;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				range.Style.Border.Right.Style = ExcelBorderStyle.Thin;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				range.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#75715e&#34;&gt;// 標題列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; title = ws.Cells[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, cols];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				title.Style.Fill.PatternType = ExcelFillStyle.Solid; &lt;span style=&#34;color:#75715e&#34;&gt;// 設定背景填色方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				title.Style.Fill.BackgroundColor.SetColor(Color.LightGray);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			Debug.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;未列印資料，請檢查是否傳入資料為空，或指定類別未具有公開且加上 DisplayName 的屬性。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		excel.Save(); &lt;span style=&#34;color:#75715e&#34;&gt;// 儲存 Excel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;//output.Position = 0; // 如果是使用 stream 的方式讓人下載，請記得將指標移回資料起始&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; output;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testClass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;	[DisplayName(&amp;#34;編號&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; Guid id { &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; } = Guid.NewGuid();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;	[DisplayName(&amp;#34;名稱&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; name { &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;結語&#34;&gt;結語&lt;/h2&gt;
&lt;p&gt;這次是為了把小工具記下來，方便以後在外可以直接開網站起來左手抄右手所以硬是丟了一篇出來；關於基本操作的部份說明實在有點偷懶，以後有機會再進行補充和功能示範。現在還請海涵，幸虧官方的 Github 頁面 Wiki 實在相當完善，對操作有疑問的朋友可以先按照官方 Wiki 跑一次，相信可以非常快上手。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/EPPlusSoftware/EPPlus/wiki&#34;&gt;EPPlus Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.darkthread.net/blog/epplus/&#34;&gt;比 NPOI 更討喜的 Excel 元件 - EPPlus! - 黑暗執行緒&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/rumeng/p/3785748.html&#34;&gt;導出 Excel 之 Epplus 使用教程 - Wico&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dotblogs.com.tw/malonestudyrecord/2018/03/21/103124&#34;&gt;使用 EPPLUS 操控 Excel - 碼農的學習日誌&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.itread01.com/content/1543037183.html&#34;&gt;.Net Excel 匯出圖表Demo(柱狀圖，多標籤頁) - IT閱讀&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>我要訂便當 (3): 用 Python &#43; Line Notify 傳送通知</title>
      <link>https://igouist.github.io/post/2020/04/bandon-3-line-notify/</link>
      <pubDate>Sun, 05 Apr 2020 19:36:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/04/bandon-3-line-notify/</guid>
      <description>&lt;p&gt;前情提要：&lt;/p&gt;
&lt;p&gt;在 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt; 中，我們嘗試了用 Selenium 控制瀏覽器去取回訂便當團購網的訂單內容&lt;/p&gt;
&lt;p&gt;而在 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;我要訂便當(2) —— 用 Python + Sqlite 儲存訂單&lt;/a&gt; 中，我們使用 Sqlite 達到將訂單儲存起來以判斷是否有新的訂單，因此這邊的下一步就是需要進行通知。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目標：&lt;strong&gt;使用 Line Notify，當有新訂單的時候就發送通知&lt;/strong&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://notify-bot.line.me/zh_TW/&#34;&gt;Line Notify&lt;/a&gt;&lt;/strong&gt; 是 Line 上面的通知服務，不像 Line@ 一樣可以有品牌有互動等等，Line Notify 純粹就是訊息通知；但同時對比 Line@ 最近吵得沸沸揚揚的收費和大量跳槽，Line Notify 則是免費的服務。&lt;/p&gt;
&lt;p&gt;Line Notify 的運作上分為發送訊息和接受訊息。當我們使用 Line 帳號申請 Line Notify 的服務後就可以得到一個 Access Token，藉由這組 Access Token 就能夠讓我們的程式和服務去發送通知。這些通知會由一個叫做 LINE Notify 的官方帳號發送給有訂閱這個通知的人。&lt;/p&gt;
&lt;p&gt;基於這個工作原理，像是需要經營品牌的服務就不太適合 Line Notify，反之像是&lt;strong&gt;伺服器斷線、設備超載等等這些個人通知性質比較高的服務就很適合使用 Line Notify&lt;/strong&gt;。這點從 &lt;a href=&#34;https://notify-bot.line.me/zh_TW/&#34;&gt;Line Notify 網頁&lt;/a&gt; 下方的示意圖也可以略知一二。&lt;/p&gt;
&lt;p&gt;當然像我們這次的需求是「如果有新的團購便當 就 通知我」，相當符合使用場景，因此這邊就嘗試使用看看並記錄下來。&lt;/p&gt;
&lt;p&gt;如果需要更多 Line Notify 的說明，保哥的這篇 &lt;a href=&#34;https://blog.miniasp.com/post/2020/02/17/Go-Through-LINE-Notify-Without-Any-Code&#34;&gt;上手 LINE Notify 不求人：一行代碼都不用寫的推播通知方法&lt;/a&gt; 介紹的更為完整，推薦參閱。當然，也可以閱讀 &lt;a href=&#34;https://notify-bot.line.me/doc/en/&#34;&gt;Line Notify 官方文件&lt;/a&gt;。那麼，我們開始吧～&lt;/p&gt;
&lt;h2 id=&#34;申請-line-notify&#34;&gt;申請 Line Notify&lt;/h2&gt;
&lt;p&gt;首先先到 &lt;a href=&#34;https://notify-bot.line.me/zh_TW/&#34;&gt;Line Notify&lt;/a&gt; 的頁面右上角登入&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VAJV0X1.webp&#34;
  alt=&#34;&#34;width=&#34;1221&#34; height=&#34;926&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;登入之後，從右上角選擇 個人頁面&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Rizyl5e.webp&#34;
  alt=&#34;&#34;width=&#34;233&#34; height=&#34;221&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著可以看到下面這個畫面：如果已經有申請過權杖或服務，就會顯示在上半部分的「已連動的服務」
而我們現在需要的是下方的發行權杖



&lt;img
  src=&#34;https://image.igouist.net/zNivpLD.webp&#34;
  alt=&#34;&#34;width=&#34;1053&#34; height=&#34;890&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著進到選擇通知視窗的畫面，在這邊可以選擇是直接通知你自己 或是將通知傳送到某個群組



&lt;img
  src=&#34;https://image.igouist.net/zt2JUrI.webp&#34;
  alt=&#34;&#34;width=&#34;529&#34; height=&#34;768&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;選擇通知對象之後，就會顯示存取用的 Token。由於&lt;strong&gt;這組 Token 只會顯示一次，請自己複製下來存好！&lt;/strong&gt;



&lt;img
  src=&#34;https://image.igouist.net/Mob0tR6.webp&#34;
  alt=&#34;&#34;width=&#34;527&#34; height=&#34;444&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;存取完畢後就可以看到這次申請的服務已經加到已連動的服務囉



&lt;img
  src=&#34;https://image.igouist.net/cT5omou.webp&#34;
  alt=&#34;&#34;width=&#34;1002&#34; height=&#34;880&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;並且 Line 也會跳出已連動的通知



&lt;img
  src=&#34;https://image.igouist.net/NegLeh6.webp&#34;
  alt=&#34;&#34;width=&#34;350&#34; height=&#34;205&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;到這邊就算是申請完畢了，接下來就要來測試一下是否能發送。&lt;/p&gt;
&lt;h2 id=&#34;發送-line-notify&#34;&gt;發送 Line Notify&lt;/h2&gt;
&lt;p&gt;這邊測試的 Code 主要參考 &lt;a href=&#34;https://bustlec.github.io/note/2018/07/10/line-notify-using-python/&#34;&gt;使用 Python 實作發送 LINE Notify 訊息 - Bustle C.&lt;/a&gt;，感謝前人的足跡。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; requests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;lineNotifyMessage&lt;/span&gt;(token, msg):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bearer &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; token, &lt;span style=&#34;color:#75715e&#34;&gt;# 權杖，Bearer 的空格不要刪掉呦&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;application/x-www-form-urlencoded&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    payload &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;: msg}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Post 封包出去給 Line Notify&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;post(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://notify-api.line.me/api/notify&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        headers&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;headers, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        params&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status_code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Line Notify 測試&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;token &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;把你的 Token 放在這&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; lineNotifyMessage(token, message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(result) &lt;span style=&#34;color:#75715e&#34;&gt;# 印一下回傳代碼&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;執行 py 檔之後就可以收到訊息囉！



&lt;img
  src=&#34;https://image.igouist.net/v32rItf.webp&#34;
  alt=&#34;&#34;width=&#34;353&#34; height=&#34;272&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果回傳成功，&lt;code&gt;r.status_code&lt;/code&gt; 就會顯示 &lt;code&gt;200&lt;/code&gt;；而如果像是 Token 已經被撤銷等，則會得到 &lt;code&gt;401&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;關於 200, 401.. 這些回傳的狀態碼，可以參閱 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi/#%E9%97%9C%E6%96%BC-http-status-code&#34;&gt;HTTP Status&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;另外當然也可以傳送圖片或貼圖，請參閱 &lt;a href=&#34;https://www.oxxostudio.tw/articles/201806/line-notify.html&#34;&gt;自建 LINE Notify 訊息通知 - Oxxo studio&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;和訂便當腳本結合&#34;&gt;和訂便當腳本結合&lt;/h2&gt;
&lt;p&gt;接著我們就將這個寄發 Line Notify 的 Function 給放回我們之前的訂便當腳本中。並在發現訂單異動的部分去呼叫 &lt;code&gt;lineNotifyMessage&lt;/code&gt; 來通知我們&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-python=47&#34; data-lang=&#34;python=47&#34;&gt;# ...
print(&amp;#34;偵測到訂單變動！&amp;#34;)
# 做點通知的事
message = &amp;#34;訂單已變動，請到 https://dinbendon.net/ 確認！&amp;#34;
result = lineNotifyMessage(token, message)
print_order(order)
# ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/j6bkIHg.webp&#34;
  alt=&#34;&#34;width=&#34;348&#34; height=&#34;103&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;打完收工！接著不管是要 比對新舊訂單並發送這次新的訂單資訊；或者是要順便發送目前的訂單數量等等，都是只差在訊息內容的修改而已（我是直接發送 &lt;code&gt;&amp;quot;,&amp;quot;.join(orderList)&lt;/code&gt; 來看目前有哪些訂單）屆此已經可以宣告收工囉。&lt;/p&gt;
&lt;h2 id=&#34;心得&#34;&gt;心得&lt;/h2&gt;
&lt;p&gt;在像這種單純通知的場合中，Line Notify 有架設簡單、即時方便的特性。並且也已經提供了和 Github 等連接的服務，因此像是伺服器斷線或是有新的提取要求、甚至是爬蟲去看手遊有沒有新活動時，都可以考慮建立一個群組並用 Line Notify 來達到低成本推播的要求，可謂相當方便！希望將來能再回來抄自己這篇把各種推播提醒都拉到 Line Notify 集中管理，&lt;del&gt;不然除了機器人也不會有人 Line 我…&lt;/del&gt;&lt;/p&gt;
&lt;h2 id=&#34;我要訂便當系列&#34;&gt;我要訂便當系列&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;我要訂便當(2) —— 用 Python + Sqlite 儲存訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;我要訂便當(3) —— 用 Python + Line Notify 傳送通知&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;我要訂便當(4) —— 將 Python 腳本部署上 Heroku&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-5-heroku-debug/&#34;&gt;我要訂便當(5) —— Heroku 填坑小記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.miniasp.com/post/2020/02/17/Go-Through-LINE-Notify-Without-Any-Code&#34;&gt;上手 LINE Notify 不求人：一行代碼都不用寫的推播通知方法 - The Will Will Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.oxxostudio.tw/articles/201806/line-notify.html&#34;&gt;自建 LINE Notify 訊息通知 - Oxxo studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blogger.jackkuo.org/2019/01/line-notify.html&#34;&gt;LINE Notify 初嚐心得 - JackKuo&amp;rsquo;s Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.evanlin.com/go-line-notify/&#34;&gt;[Golang][LINE][教學] 如何快速建置一個 LINE Notify 的服務 - KKDAI.GITHUB.IO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://bustlec.github.io/note/2018/07/10/line-notify-using-python/&#34;&gt;使用 Python 實作發送 LINE Notify 訊息 - Bustle C.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10223413&#34;&gt;Day15-Python Line 整合應用 &amp;ndash; Line Notify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Visual studio 環境設定 —— 字型、套件、快捷鍵</title>
      <link>https://igouist.github.io/post/2020/03/visualstudio/</link>
      <pubDate>Sat, 28 Mar 2020 00:11:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/03/visualstudio/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;目標：整理一下自己用的&lt;strong&gt;字型、插件和快捷鍵&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;今天在工作時寫到一半突然藍屏，重開機之後俺的地表最強編譯器 Visual Studio 整個就開始鬧脾氣，打開專案整排都是 Error，連 System.Object 都找不到，差點往生。還好用了修復功能之後一切恢復正常，但是載入的插件和一些個人設定就這樣重置了……&lt;/p&gt;
&lt;p&gt;為了之後可能還會遇到相同的事情，這邊就先將平常的 Visual Studio 環境用到的設定做一份紀錄，之後遇到新插件或是什麼功能也可以回來更新這篇文，如此一來下次又被洗白的時候就可以回來參考了。&lt;/p&gt;
&lt;p&gt;這邊主要會記錄三個部分，並隨時可能更新。主要是字型、使用的插件以及常用的快捷鍵。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;不過我的 Visual Studio 也被洗白了所以這篇會跟著找回失落插件的歷程慢慢補上QQ&lt;/del&gt;&lt;/p&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#字型&#34;&gt;字型&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#consolas&#34;&gt;Consolas&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#firacode&#34;&gt;FiraCode&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#更紗黑體&#34;&gt;更紗黑體&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#ink-free&#34;&gt;Ink Free&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#變更-visual-studio-環境字體&#34;&gt;變更 Visual Studio 環境字體&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#擴充套件&#34;&gt;擴充套件&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#codemaid&#34;&gt;CodeMaid&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#codemaintainibility&#34;&gt;CodeMaintainibility&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#editor-guidelines&#34;&gt;Editor Guidelines&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#codeblockendtag&#34;&gt;CodeBlockEndTag&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#smartpaster&#34;&gt;SmartPaster&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#time-savers&#34;&gt;Time Savers&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#claudiaide&#34;&gt;ClaudiaIDE&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#visual-studio-iconizer&#34;&gt;Visual Studio Iconizer&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#output-enhancer--metaoutput&#34;&gt;Output enhancer &amp;amp; MetaOutput&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#設定&#34;&gt;設定&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#變更-visual-studio-程式碼的配色為新版本-2019&#34;&gt;變更 Visual Studio 程式碼的配色為新版本 (2019)&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#開啟內嵌提示&#34;&gt;開啟內嵌提示&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#對目前所在行醒目提示&#34;&gt;對目前所在行醒目提示&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#調整索引標籤設定&#34;&gt;調整索引標籤設定&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#配色主題&#34;&gt;配色主題&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#one-dark-pro&#34;&gt;One Dark Pro&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#快捷鍵&#34;&gt;快捷鍵&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#偵錯&#34;&gt;偵錯&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#檢視&#34;&gt;檢視&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#編輯&#34;&gt;編輯&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#參考資料&#34;&gt;參考資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;字型&#34;&gt;字型&lt;/h2&gt;
&lt;p&gt;字型部分必定、絕對要使用&lt;strong&gt;等寬字型&lt;/strong&gt;，這是必要的前提也是絕對的共識，畢竟你不會希望有什麼神秘空白，或是推個版就排版大炸裂之類的。&lt;/p&gt;
&lt;p&gt;推薦先參閱 &lt;a href=&#34;https://kevintsengtw.blogspot.com/2014/08/blog-post.html&#34;&gt;換個好字型讓程式開發有效率&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2021.01.27 補充：字型也可以看 &lt;a href=&#34;https://devfonts.gafi.dev/&#34;&gt;devfonts&lt;/a&gt; 。裡面直接放了超多 Coding 常用字型，也可以貼上 Code 直接進行比較，相當貼心。想逛一下挑個順眼字型的朋友直接試試唄。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;consolas&#34;&gt;Consolas&lt;/h3&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/az9u0B3.webp&#34;
  alt=&#34;&#34;width=&#34;481&#34; height=&#34;279&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

內建就有的字體，個人認為在不想另外下載字體的時候是相當優質的選擇。&lt;/p&gt;
&lt;p&gt;看起來粗粗圓圓的很放鬆。&lt;/p&gt;
&lt;h3 id=&#34;firacode&#34;&gt;FiraCode&lt;/h3&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9OJpMXL.webp&#34;
  alt=&#34;&#34;width=&#34;595&#34; height=&#34;301&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;a href=&#34;https://github.com/tonsky/FiraCode&#34;&gt;FiraCode&lt;/a&gt;，整體乾淨優雅，尤其是連字能使得整個 Code 更有滑順的感覺，十分推薦。&lt;/p&gt;
&lt;p&gt;然而，FiraCode 雖然陪伴我一段不短的時間，但還是有一個致命的缺陷：不支援中文！&lt;/p&gt;
&lt;p&gt;於是後來我基本上都使用下面介紹的這款&lt;/p&gt;
&lt;h3 id=&#34;更紗黑體&#34;&gt;更紗黑體&lt;/h3&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yVMe5wC.webp&#34;
  alt=&#34;&#34;width=&#34;471&#34; height=&#34;309&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

&lt;a href=&#34;https://github.com/be5invis/Sarasa-Gothic&#34;&gt;更紗黑體&lt;/a&gt; 除了同樣支援連字以外，更支援多國語系。例如繁中就是有標註 TC 的字形，看見中英文都套用上去就是一陣舒服。是現在我的主力。&lt;/p&gt;
&lt;p&gt;至於不想用等寬字體的朋友，都看到這裡了，沒關係。下面這套推薦給你們&lt;/p&gt;
&lt;h3 id=&#34;ink-free&#34;&gt;Ink Free&lt;/h3&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/EShjvS5.webp&#34;
  alt=&#34;&#34;width=&#34;389&#34; height=&#34;289&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

就是這麼直接！這麼舒服！如果你的同事要過來 Code Review，不要猶豫字體直接換下去！&lt;/p&gt;
&lt;h2 id=&#34;變更-visual-studio-環境字體&#34;&gt;變更 Visual Studio 環境字體&lt;/h2&gt;
&lt;p&gt;前面介紹了一些好用的字體，以及 &lt;a href=&#34;https://devfonts.gafi.dev/&#34;&gt;devfonts&lt;/a&gt; 這個超讚網站。接著就讓我們來設定 Visual Studio 的字體吧。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;工具 &amp;gt; 選項 &amp;gt; 環境 &amp;gt; 字型與色彩&lt;/code&gt; 中，能設定當前使用的字型。其中顯示設定可以選擇要變更哪個區塊的字型，最常用的應該就是改程式碼字型的「文字編輯器」了。&lt;/p&gt;
&lt;p&gt;這邊推薦一下我個人還會調整的部份：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CodeLens&lt;/li&gt;
&lt;li&gt;陳述式完成&lt;/li&gt;
&lt;li&gt;編譯器工具提示&lt;/li&gt;
&lt;li&gt;環境&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/m2zfp6K.webp&#34;
  alt=&#34;Image&#34;width=&#34;367&#34; height=&#34;508&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;影響到的範圍會有&lt;/p&gt;
&lt;p&gt;環境相關的字體，例如上方的工具列&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/H5t6Jun.webp&#34;
  alt=&#34;Image&#34;width=&#34;611&#34; height=&#34;65&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/9hYcnfn.webp&#34;
  alt=&#34;Image&#34;width=&#34;306&#34; height=&#34;259&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;按下 &lt;code&gt;Alt + Enter&lt;/code&gt; 的小提示、方法上方的 CodeLen 小提示（N 個參考那個）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lHf3SHe.webp&#34;
  alt=&#34;Image&#34;width=&#34;576&#34; height=&#34;157&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;自動補完和註解說明等等&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/SaMzVkA.webp&#34;
  alt=&#34;Image&#34;width=&#34;575&#34; height=&#34;231&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;另外我個人還會把清單裡任何加上 &lt;code&gt;[]&lt;/code&gt; 的部份的字型也改掉：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有文具工具&lt;/li&gt;
&lt;li&gt;監看式、區域變數及自動變數工具視窗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果再搭配上&lt;a href=&#34;#%E9%85%8D%E8%89%B2%E4%B8%BB%E9%A1%8C&#34;&gt;配色主題&lt;/a&gt;就可以把整個 IDE 弄得更賞心悅目囉！&lt;/p&gt;
&lt;h2 id=&#34;擴充套件&#34;&gt;擴充套件&lt;/h2&gt;
&lt;h3 id=&#34;codemaid&#34;&gt;CodeMaid&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=SteveCadwallader.CodeMaid&#34;&gt;CodeMaid&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;極實用，我平常主要用到的功能是看&lt;strong&gt;複雜度&lt;/strong&gt;和&lt;strong&gt;自動排版&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pf897N7.webp&#34;
  alt=&#34;&#34;width=&#34;289&#34; height=&#34;321&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

安裝之後可以開啟 CodeMaid Spade，其中函式右側的就是該函式的複雜度，複雜度指得就是該函式中各種不同狀況的路徑數量，例如一個 IF 就會有兩條路徑。&lt;/p&gt;
&lt;p&gt;而複雜度相當高的時候 CodeMaid Spade 的字體會變成紅色，屆時就應該思考這個函式是否做了太多事情，也就是有太多&lt;a href=&#34;https://igouist.github.io/post/2020/10/oo-10-single-responsibility-principle&#34;&gt;職責&lt;/a&gt;？是否應該把部分功能抽出來？平常可以迅速地提供參考。關於複雜度相關的工具也可以參閱：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/09/visual-studio-microsoft-codelens-code.html&#34;&gt;Visual Studio - Microsoft CodeLens Code Health Indicator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/02/visual-studio-code-metrics-viewer-2013.html&#34;&gt;Visual Studio 計算程式碼度量 - Code Metrics Viewer 2013&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/sb2440G.webp&#34;
  alt=&#34;&#34;width=&#34;556&#34; height=&#34;485&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

除了看見複雜度以外，這個插件的主要功能就在於程式碼的排版和整理，甚至可以設定成每當儲存時自動排版一次。對我這種懶人來說可是一大福音。但要小心跟其他人協作的時候如果全部重新排版可能動到人家的 Code 而且推送時的變更會爆炸多，要稍微注意。平常就養成順手 &lt;code&gt;Ctrl + K&lt;/code&gt;、&lt;code&gt;Ctrl + D&lt;/code&gt; 的習慣會更好。&lt;/p&gt;
&lt;h3 id=&#34;codemaintainibility&#34;&gt;CodeMaintainibility&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面： &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=ognjen-babic.code-maintainibility&#34;&gt;Code Maintainibility&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;提到複雜度，也可以安裝這套 Code Maintainibility，在看複雜度的時候能夠更快更方便！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/90Ox0rF.webp&#34;
  alt=&#34;&#34;width=&#34;1423&#34; height=&#34;817&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;一樣在「管理擴充功能」直接搜尋就可以了，但這個套件似乎有改版過。我更新了 Visual Studio 才搜尋得到，如果找不到的朋友可以先嘗試更新看看。&lt;/p&gt;
&lt;p&gt;安裝之後就可以直接在各個 Function 上方的 CodeLens 直接看到複雜度指標囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/No3C6yH.webp&#34;
  alt=&#34;&#34;width=&#34;592&#34; height=&#34;253&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;點開來也會顯示各項指標，包括可維護性、霍爾斯特德複雜度等等&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/aOCbIp1.webp&#34;
  alt=&#34;&#34;width=&#34;1077&#34; height=&#34;251&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;也可以在設定之中調整預設顯示的指標&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/amDpQtp.webp&#34;
  alt=&#34;&#34;width=&#34;1147&#34; height=&#34;678&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣在撰寫和重構程式碼的時候就可以迅速又清楚看見複雜度指標，&lt;s&gt;心裡也至少有個底&lt;/s&gt;，可以說是方便不少呢&lt;/p&gt;
&lt;h3 id=&#34;editor-guidelines&#34;&gt;Editor Guidelines&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=PaulHarrington.EditorGuidelines&#34;&gt;Editor Guidelines&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;在指定字數位置&lt;strong&gt;劃出直的輔助線&lt;/strong&gt;，我都用來標示出 100 字元 和 120 字元的位置，用來提醒自己要換行，以保持程式碼可以直直地閱讀下去，而不會為了往右滾動或是因為自動斷行在奇怪地方而中斷。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yBffyf3.webp&#34;
  alt=&#34;&#34;width=&#34;1322&#34; height=&#34;455&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

可以看見右側有輔助線，通常只要長到碰到該線我就會將該行程式碼做斷行的調整。&lt;/p&gt;
&lt;p&gt;安裝之後可以在編輯器的任何位置按下右鍵，就可以增加和移除輔助線。



&lt;img
  src=&#34;https://image.igouist.net/0YqkYFI.webp&#34;
  alt=&#34;&#34;width=&#34;595&#34; height=&#34;206&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果想要在確切字元位置（如 100 字元），則可以在 &lt;code&gt;檢視 → 其他視窗 → 命令視窗&lt;/code&gt; 裡面輸入指令來加入輔助線&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Edit.AddGuideline 100 // 添加輔助線
Edit.RemoveAllGuidelines // 移除輔助線
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;關於換行的重要性，這篇 &lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/02/visual-studio-part1.html&#34;&gt;調整你的 Visual Studio - Part.1&lt;/a&gt; 說明得很仔細，並且也有上面 CodeMaid 的介紹，我之所以打算紀錄我的套件等也是基於這幾篇，推薦閱讀。&lt;/p&gt;
&lt;h3 id=&#34;codeblockendtag&#34;&gt;CodeBlockEndTag&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=KhaosPrinz.CodeBlockEndTag&#34;&gt;CodeBlockEndTag&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;主要是在&lt;strong&gt;括弧的結束部分會顯示出這個括弧所屬的區塊&lt;/strong&gt;（如附圖）。我有看見許多同事都使用替括弧上色的作法，不過我個人不喜歡太過五彩繽紛的感覺，因此強烈推薦這款插件。&lt;/p&gt;
&lt;p&gt;下載之後可以在 &lt;code&gt;工具 → 選項 → KC Extensions&lt;/code&gt; 裡面調整，有 當上括弧在畫面外時才顯示下括弧的文字，以及一律顯示（我個人都是使用一律顯示）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/sRCndm3.webp&#34;
  alt=&#34;&#34;width=&#34;778&#34; height=&#34;436&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

可以看見 IF 跟函式結束的地方都有標示出括弧對應的區塊，在多層巢狀的時候相當有幫助。（雖然說更重要的是應該避免做出多層巢狀就是了）&lt;/p&gt;
&lt;h3 id=&#34;smartpaster&#34;&gt;SmartPaster&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=martinw.SmartPaster2013&#34;&gt;SmartPaster2019&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;貼上的時候可以&lt;strong&gt;選擇貼上的格式&lt;/strong&gt;，在插件介紹頁的示意圖就能夠一目瞭然。&lt;/p&gt;
&lt;h3 id=&#34;time-savers&#34;&gt;Time Savers&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=YannDuran.VisualStudioTimeSavers&#34;&gt;Time Savers&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;單純的省時小工具，可以&lt;strong&gt;在上方的工具列幫你長出一些 建置、以管理員重開等按鍵&lt;/strong&gt;。畢竟和建置那些可以直接快捷鍵的動作不一樣，有時候沒有以系統管理員身分開 Visual Studio 的時候會遇到一些神秘錯誤，重開又挺麻煩，因此直接放顆按鈕在上面是真的省了不少時間，要記得遵守工程師的美德：懶惰。能省時就省時！&lt;/p&gt;
&lt;h3 id=&#34;claudiaide&#34;&gt;ClaudiaIDE&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=kbuchi.ClaudiaIDE&#34;&gt;ClaudiaIDE&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這個跟效率就沒啥太大關係了，所以特別放在壓軸哈，這插件主要是用來&lt;strong&gt;修改編譯區塊的背景&lt;/strong&gt;使用的。&lt;/p&gt;
&lt;p&gt;剛安裝下來之後打開 Visual Studio 就會看到一個妹子。



&lt;img
  src=&#34;https://image.igouist.net/op0y1wf.webp&#34;
  alt=&#34;&#34;width=&#34;1619&#34; height=&#34;978&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當然這個背景圖是可以自由變更的，打開 &lt;code&gt;工具 → 選項 → ClaudiaIDE&lt;/code&gt; 就會看到以下的設置畫面



&lt;img
  src=&#34;https://image.igouist.net/O3KAm0N.webp&#34;
  alt=&#34;&#34;width=&#34;744&#34; height=&#34;632&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;主要會變動的地方會有 Opacity 透明度、File Path 圖片路徑 以及 Image Stretch 圖片填滿或是延展 這些設定，根據個人經驗，盡量用深色背景圖加上透明會比較順眼，主要還是要以不干擾閱讀程式碼為主&lt;/p&gt;
&lt;p&gt;像我這麼低調的換個藍底的 VS Logo 就足夠竊喜好一陣子了



&lt;img
  src=&#34;https://image.igouist.net/gjlxy52.webp&#34;
  alt=&#34;&#34;width=&#34;1324&#34; height=&#34;848&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;順帶一提我同事的背景圖是用這張，嗯……



&lt;img
  src=&#34;https://image.igouist.net/YER0385.webp&#34;
  alt=&#34;&#34;width=&#34;1205&#34; height=&#34;700&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;

（載下來示範截圖之後立馬換回來，實在過於微妙）&lt;/p&gt;
&lt;h3 id=&#34;visual-studio-iconizer&#34;&gt;Visual Studio Iconizer&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=OlegTarasov.VisualStudioIconizerforVisualStudio15&#34;&gt;Visual Studio Iconizer&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這個則是讓你的 Visual Studio 質感飛升的關鍵：它會替你的工具欄加上 icon！&lt;/p&gt;
&lt;p&gt;先來看看原本的工具視窗釘選之後長怎樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/s0zVMsN.webp&#34;
  alt=&#34;Image&#34;width=&#34;39&#34; height=&#34;161&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝後：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/004mjQq.webp&#34;
  alt=&#34;Image&#34;width=&#34;41&#34; height=&#34;141&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當然如果怕找不到的話，也可以圖文並行（&lt;code&gt;工具 → 選項 → iconizer&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ScUCjeU.webp&#34;
  alt=&#34;Image&#34;width=&#34;37&#34; height=&#34;276&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;output-enhancer--metaoutput&#34;&gt;Output enhancer &amp;amp; MetaOutput&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;市集頁面：&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=NikolayBalakin.Outputenhancer&#34;&gt;Output enhancer&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;最後來介紹個實用的 Output enhancer：替你的輸出視窗上色&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KGW1DrK.webp&#34;
  alt=&#34;Image&#34;width=&#34;700&#34; height=&#34;231&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;警告跟錯誤等等就會更顯眼囉！&lt;/p&gt;
&lt;p&gt;如果想要更進一步也可以使用他們的另一款整合過的 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=ViacheslavLozinskyi.MetaOutput-2019&#34;&gt;MetaOutput&lt;/a&gt;（&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=ViacheslavLozinskyi.MetaOutput-2022&#34;&gt;2022&lt;/a&gt;），輸出視窗就會變為條列式：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IRBlGGv.webp&#34;
  alt=&#34;Image&#34;width=&#34;842&#34; height=&#34;330&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;除了會折疊輸出訊息以外，也能調整哪部分訊息可以省略：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KQutBi1.webp&#34;
  alt=&#34;Image&#34;width=&#34;336&#34; height=&#34;73&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;也可以進行搜尋：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/0lNna9g.webp&#34;
  alt=&#34;Image&#34;width=&#34;388&#34; height=&#34;323&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;個人覺得挺方便的。雖然日常狀況還是等紅字跳出來再說 XD&lt;/p&gt;
&lt;h2 id=&#34;設定&#34;&gt;設定&lt;/h2&gt;
&lt;h3 id=&#34;變更-visual-studio-程式碼的配色為新版本-2019&#34;&gt;變更 Visual Studio 程式碼的配色為新版本 (2019)&lt;/h3&gt;
&lt;p&gt;Visual Studio 2019 預設的程式碼配色會是 2017 版本的，因此可以先調整更改成 2019 版本。&lt;/p&gt;
&lt;p&gt;更改的位置在 &lt;code&gt;工具 &amp;gt; 選項 &amp;gt; 文字編輯器 &amp;gt; C# (看個人使用語言) &amp;gt; 進階&lt;/code&gt; 接著拉至最底找到 &lt;code&gt;編輯器色彩配置&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/lbUP19r.webp&#34;
  alt=&#34;&#34;width=&#34;677&#34; height=&#34;242&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;把它更改成 2019 版本的就可以囉&lt;/p&gt;
&lt;p&gt;變更前：



&lt;img
  src=&#34;https://image.igouist.net/IMr2ViF.webp&#34;
  alt=&#34;&#34;width=&#34;918&#34; height=&#34;364&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;變更後：



&lt;img
  src=&#34;https://image.igouist.net/s0zYjmu.webp&#34;
  alt=&#34;&#34;width=&#34;934&#34; height=&#34;340&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到變數、方法都有上色了，在整排 Linq 連發的 Code 裡面可是相當實用呢&lt;/p&gt;
&lt;h3 id=&#34;開啟內嵌提示&#34;&gt;開啟內嵌提示&lt;/h3&gt;
&lt;p&gt;這邊推薦把「內嵌提示」這個實驗性功能打開，可以大大增加程式碼的可讀性。&lt;/p&gt;
&lt;p&gt;首先讓我們先到 &lt;code&gt;選項 &amp;gt; 文字編輯器 &amp;gt; C# &amp;gt; 進階&lt;/code&gt;，並且往下拉就可以看到內嵌提示。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/FMERRDD.webp&#34;
  alt=&#34;image-20210830065440439&#34;width=&#34;735&#34; height=&#34;423&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;其中「&lt;strong&gt;顯示內嵌參數名稱&lt;/strong&gt;」勾選起來的話，就會在呼叫方法時顯示該參數的名稱，如下圖的 &lt;code&gt;startIndex&lt;/code&gt; 和 &lt;code&gt;length&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7QCEEkY.webp&#34;
  alt=&#34;image-20210830065736932&#34;width=&#34;490&#34; height=&#34;98&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;像這種呼叫方法時傳遞的常數，例如 &lt;code&gt;0&lt;/code&gt;、&lt;code&gt;false&lt;/code&gt; 等等，加上參數名稱就能大大提升可讀性。&lt;/p&gt;
&lt;p&gt;但當我們是傳遞變數的時候，常常都已經針對該物件做好妥善的命名了，所以可以把子項的「當參數名稱符合方法的意圖時，不出現提示」也勾選起來，避免命名已經足夠描述內容時反而造成干擾。&lt;/p&gt;
&lt;p&gt;接著「&lt;strong&gt;顯示內嵌類型提示&lt;/strong&gt;」也可以勾選起來，顧名思義就是會在洽當的時候提醒你「這東西是這個型別呦！」的小助手。&lt;/p&gt;
&lt;p&gt;其中我會關閉第一項「顯示有推斷類型之變數的提示」，它的效果就是每當你使用 &lt;code&gt;var&lt;/code&gt; 的時候就會顯示推測的型別，例如：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nGyRm6J.webp&#34;
  alt=&#34;image-20210830071205401&#34;width=&#34;406&#34; height=&#34;74&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到 &lt;code&gt;var&lt;/code&gt; 的後面會補上型別。&lt;del&gt;不過這樣我排版就亂掉了，而且我就是打算把型別丟給 C# 處理才用 var 的所以掰掰&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;而第二項「&lt;strong&gt;顯示 Lambda 參數類型的提示&lt;/strong&gt;」這個就相當推薦打開了，作用的方式會像這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/RVqvtcJ.webp&#34;
  alt=&#34;image-20210830071404915&#34;width=&#34;480&#34; height=&#34;118&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;當你在使用 Linq 的時候，就能更清楚知道現在自己在操作的是哪個型別、哪個部分。在 &lt;code&gt;Join&lt;/code&gt;、&lt;code&gt;Groupby&lt;/code&gt; 等等需要對串列連續地進行處理的時候，能夠發揮莫大的功效，特別強烈推薦要打開。&lt;/p&gt;
&lt;h3 id=&#34;對目前所在行醒目提示&#34;&gt;對目前所在行醒目提示&lt;/h3&gt;
&lt;p&gt;這段其實是發完文之後才補充的啦，這邊要推薦一個好用的設定：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;工具 &amp;gt; 選項 &amp;gt; 環境 &amp;gt; 字型與色彩&lt;/code&gt; 然後在下拉式選單找到 &lt;code&gt;反白顯示目前的行&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;就可以對游標所在的那行做醒目提示囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qvh8i8l.webp&#34;
  alt=&#34;&#34;width=&#34;408&#34; height=&#34;251&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;圖中的綠色就是醒目提示。&lt;/p&gt;
&lt;p&gt;之前和大前輩聊到這個反白行，前輩表示他很討厭，因為他不太需要一條會干擾的色塊告訴他正在這&lt;/p&gt;
&lt;p&gt;但像我這種菜雞，按著 Ctrl + G 就不知道自己飛哪裡去了，還是標記一下好哈哈。&lt;/p&gt;
&lt;h3 id=&#34;調整索引標籤設定&#34;&gt;調整索引標籤設定&lt;/h3&gt;
&lt;p&gt;個人習慣將索引標籤放在右側，如此一來就可以更清楚地看到檔案名稱，而且條列式地由上往下看還是比較符合平時看 Code 的習慣。&lt;/p&gt;
&lt;p&gt;索引標籤的位置可以在 &lt;code&gt;工具 &amp;gt; 選項 &amp;gt; 環境 &amp;gt; 索引標籤和視窗&lt;/code&gt; 裡進行調整：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/sIRwNBz.webp&#34;
  alt=&#34;Image&#34;width=&#34;740&#34; height=&#34;491&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這邊也強烈建議將 專案/路徑 分組勾選開來，可以幫助我們更快找到開啟的索引：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/urbQnTd.webp&#34;
  alt=&#34;Image&#34;width=&#34;254&#34; height=&#34;258&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在 Visual Studio 2022 時，可以更進一步打開依專案著色索引的功能：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4mmuJqs.webp&#34;
  alt=&#34;Image&#34;width=&#34;437&#34; height=&#34;268&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5fVIZlL.webp&#34;
  alt=&#34;Image&#34;width=&#34;260&#34; height=&#34;210&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣開一堆索引要找的時候就更快了！&lt;/p&gt;
&lt;p&gt;&lt;del&gt;如果從來不用索引標籤，就是無情的 &lt;code&gt;Ctrl T&lt;/code&gt; 或是 &lt;code&gt;Ctrl Tab&lt;/code&gt; 打全場的也可以不用上色沒關係就是了…&lt;/del&gt;&lt;/p&gt;
&lt;h2 id=&#34;配色主題&#34;&gt;配色主題&lt;/h2&gt;
&lt;h3 id=&#34;one-dark-pro&#34;&gt;One Dark Pro&lt;/h3&gt;
&lt;p&gt;這邊推薦我在 Visual Studio Code 也很愛用的主題：&lt;strong&gt;One Dark Pro&lt;/strong&gt;，前陣子才發現居然在 Visual Studio 上也能看到熟悉的配色，馬上就安裝下來了。&lt;/p&gt;
&lt;p&gt;因為它也是擴充套件之一，所以一樣讓我們打開 &lt;code&gt;延伸模組 &amp;gt; 管理延伸模組&lt;/code&gt;，並搜尋 &lt;code&gt;One Dark Pro&lt;/code&gt;，找到並安裝下來：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pXGqoqd.webp&#34;
  alt=&#34;&#34;width=&#34;938&#34; height=&#34;578&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;下載好之後需要關閉 Visual Studio 安裝一下。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/CGuQ6ge.webp&#34;
  alt=&#34;&#34;width=&#34;431&#34; height=&#34;324&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;安裝好了之後重新開啟 Visual Studio，並前往 &lt;code&gt;工具 &amp;gt; 選項&lt;/code&gt;，在左邊找到 &lt;code&gt;環境&lt;/code&gt;，就可以從色彩佈景主題裡選擇 One Dark Pro 囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZrtztVV.webp&#34;
  alt=&#34;&#34;width=&#34;743&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/4oBef8K.webp&#34;
  alt=&#34;&#34;width=&#34;1613&#34; height=&#34;969&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;果然還是熟悉的配色最對味～大家也可以嘗試安裝看看配色主題呦！會有一番新滋味呢。&lt;/p&gt;
&lt;h2 id=&#34;快捷鍵&#34;&gt;快捷鍵&lt;/h2&gt;
&lt;p&gt;最後大概整理一下平常會按到的快捷鍵，方便上班在外可以直接回來偷看。（希望有朝一日能夠寫起來時雙手不離鍵盤 XD）&lt;/p&gt;
&lt;h3 id=&#34;偵錯&#34;&gt;偵錯&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;執行(debug)：F5&lt;/li&gt;
&lt;li&gt;執行(non debug)：Ctrl + F5&lt;/li&gt;
&lt;li&gt;全部儲存：Ctrl + Shift + S&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;單步執行：F11 (F10)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;切換斷點：F9&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;啟用/停用斷點：Ctrl + F9&lt;/li&gt;
&lt;li&gt;刪除所有斷點：Ctrl + Shift + F9&lt;/li&gt;
&lt;li&gt;（單元測試）對全部測試：Ctrl + R, A&lt;/li&gt;
&lt;li&gt;（單元測試）對全部偵錯：Ctrl + R, Ctrl + A&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;檢視&#34;&gt;檢視&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;程式碼編輯器分頁切換 ：Ctrl+TAB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;顯示屬性窗口：F4&lt;/li&gt;
&lt;li&gt;關閉目前視窗：Ctrl + F4&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移至定義：F12&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;列出參考：Shift + F12&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移至目標：Ctrl + T =&amp;gt; 輸入目標&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;巡覽列：Ctrl + F2 =&amp;gt; Tab&lt;/li&gt;
&lt;li&gt;工具列：Alt + (對應鍵)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;線上搜尋微軟官方文件：F1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;回到編輯器區塊：F7&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2021/03/visual-studio-bookmark/&#34;&gt;書籤&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;在指定行號上新增書籤：Ctrl + K, Ctrl + K&lt;/li&gt;
&lt;li&gt;開啟書籤視窗：Ctrl + K, Ctrl + W&lt;/li&gt;
&lt;li&gt;移動到上一個書籤：Ctrl + K, Ctrl + P&lt;/li&gt;
&lt;li&gt;移動到下一個書籤：Ctrl + K, Ctrl + N&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;編輯&#34;&gt;編輯&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;選取目前文字：Ctrl + W&lt;/li&gt;
&lt;li&gt;選取同個變數：Shift + Ctrl + ↑/↓&lt;/li&gt;
&lt;li&gt;選取目標區塊：Shift + Ctrl + }&lt;/li&gt;
&lt;li&gt;註解選取範圍：Ctrl + K,C&lt;/li&gt;
&lt;li&gt;取消註解選取範圍：Ctrl + K,U&lt;/li&gt;
&lt;li&gt;選取文字改小寫：Ctrl + U&lt;/li&gt;
&lt;li&gt;選取文字改大寫：Ctrl + Shift + U&lt;/li&gt;
&lt;li&gt;呼叫出類別成員：Ctrl + J (編到一半時 tips 突然不見很好用)&lt;/li&gt;
&lt;li&gt;收攏原始碼：Ctrl + M, O&lt;/li&gt;
&lt;li&gt;展開原始碼：Ctrl + M, L&lt;/li&gt;
&lt;li&gt;收攏／展開當前區塊：Ctrl + M, Ctrl + M&lt;/li&gt;
&lt;li&gt;刪除目前這行：Ctrl + Shift + L&lt;/li&gt;
&lt;li&gt;刪除目前往後：Ctrl + Delete&lt;/li&gt;
&lt;li&gt;檔案最前面：Shift + Home&lt;/li&gt;
&lt;li&gt;檔案最後面：Shift + End&lt;/li&gt;
&lt;li&gt;選到最前面：Ctrl + Shift + Home&lt;/li&gt;
&lt;li&gt;選到最後面：Ctrl + Shift + End&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳至行號：Ctrl + G&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在上面插入一行： Ctrl + Enter&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多行選取：Shift + Alt + ↑ or ↓&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;打開右鍵選單：Shift + F10&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2014/08/blog-post.html&#34;&gt;換個好字型讓程式開發有效率&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/02/visual-studio-part1.html&#34;&gt;調整你的 Visual Studio - Part.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/02/visual-studio-part2.html&#34;&gt;調整你的 Visual Studio - Part.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/09/visual-studio-microsoft-codelens-code.html&#34;&gt;Visual Studio - Microsoft CodeLens Code Health Indicator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kevintsengtw.blogspot.com/2015/02/visual-studio-code-metrics-viewer-2013.html&#34;&gt;Visual Studio 計算程式碼度量 - Code Metrics Viewer 2013&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/HW140701/article/details/85162678?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&amp;amp;utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&#34;&gt;Visual Studio 增加每行最多字符数限制参考线&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;各位整理快捷鍵的大大們，族繁不及備載&lt;/li&gt;
&lt;li&gt;感謝辦公室門口旁邊的不知名同事，讓我發現可以放 &lt;del&gt;妹子&lt;/del&gt; 圖片在 Visual Studio 裡&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>SikuliX —— 針對圖形介面寫自動化腳本的小幫手</title>
      <link>https://igouist.github.io/post/2020/03/sikulix/</link>
      <pubDate>Sun, 22 Mar 2020 13:11:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/03/sikulix/</guid>
      <description>&lt;p&gt;最近因緣際會下開始玩一些自動化測試的小工具，發現即使不是用在正規的測試時也相當實用，畢竟我們這行能讓電腦自動幫我們省事就是一種樂趣嘛。正巧這禮拜比較忙，無法準時推出訂便當系列的續集，因此決定直接紀錄一下試玩 Sikulix 的過程，以後有需要自動化的時候也方便回來參考。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sikulix 是一款針對圖形介面編寫腳本達到自動操作&lt;/strong&gt;的軟體，操作上相當方便。主要是將目標的圖示等畫面擷取下來，再編寫程式對目標進行操作。&lt;/p&gt;
&lt;p&gt;它編寫腳本時使用的語言是 &lt;a href=&#34;https://www.jython.org/&#34;&gt;Jython&lt;/a&gt; —— 用 Java 實現的 Python，關於 Jython 的基本操作可以參見 &lt;a href=&#34;https://iowiki.com/jython/jython_overview.html&#34;&gt;Wiki 教程：Jyhton&lt;/a&gt;，對於「實現 Python？」這句話感到疑惑的朋友可以參見 &lt;a href=&#34;https://zhuanlan.zhihu.com/p/58492338&#34;&gt;知乎：各種 Python 實現的簡單介绍與比較&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;此外，本篇對於 Sikulix 的參考主要來自於&lt;a href=&#34;https://ypwalter.blogspot.com/2018/06/sikuli-sikulix.html?view=classic&#34;&gt;【測試】圖形化的自動測試 Sikuli / SikuliX 的相關技巧&lt;/a&gt; 以及 &lt;a href=&#34;https://www.tpisoftware.com/tpu/articleDetails/876&#34;&gt;Sikulix 圖形辨識自動化測試開發工具&lt;/a&gt; 兩篇，特此感謝。&lt;/p&gt;
&lt;h2 id=&#34;下載與安裝&#34;&gt;下載與安裝&lt;/h2&gt;
&lt;p&gt;首先必須先到 &lt;a href=&#34;http://sikulix.com/&#34;&gt;Sikulix.com&lt;/a&gt; 下載；進去後直接點選 Latest stable version 就會進到下載頁面，接著直接下載 IDE 來使用。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/d3LGYpJ.webp&#34;
  alt=&#34;&#34;width=&#34;1271&#34; height=&#34;714&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;如果沒有準備 Jython 環境，就需要再下載 &lt;a href=&#34;https://repo1.maven.org/maven2/org/python/jython-standalone/2.7.1/jython-standalone-2.7.1.jar&#34;&gt;Jython 獨立包&lt;/a&gt;。並放置在和 Sikulix 的 IDE 同個資料夾中。如果需要別的載入方式，可以參閱 &lt;a href=&#34;https://github.com/RaiMan/SikuliX1/wiki/How-to-make-Jython-ready-in-the-IDE&#34;&gt;How to make Jython ready in the IDE&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;準備完畢之後，直接打開 IDE 應該就能看到操作介面囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/0ciX1Ur.webp&#34;
  alt=&#34;&#34;width=&#34;1010&#34; height=&#34;693&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;實作一個小測試&#34;&gt;實作一個小測試&lt;/h2&gt;
&lt;p&gt;開啟 IDE 之後，可以看見左邊的程式區，右邊的輸出訊息，以及上面一排操作選單。我們先前提過 Sikulix 是一款以圖形操作的自動化工具，因此我們最常用到的會是上方的「螢幕截圖」和「插入圖片」這兩個能把圖放進去的功能。&lt;/p&gt;
&lt;p&gt;為了做基本的測試，這邊我打開 Windows 內建的小算盤，並且使用上方工具列的螢幕截圖功能。&lt;strong&gt;按下左上角的螢幕截圖後就可以圈選目標&lt;/strong&gt;，這邊我嘗試將計算機的按鈕框選起來。一框選起來之後它就會幫我們把圖放到左邊的程式區準備讓我們使用。&lt;/p&gt;
&lt;p&gt;（註：如果不想看見圖片的影像，而想要直接看圖的檔名，如圖片是使用插入圖片要以顯示檔案名稱為主的時候，可以從上面工具列的「檢視」中做切換。）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/exsTGph.webp&#34;
  alt=&#34;&#34;width=&#34;1552&#34; height=&#34;938&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著讓我們先存檔一次（養成好習慣！不過即使尚未存檔，在執行腳本前也會要求存檔），會跳出存檔畫面，這邊示範就先隨便取個名字試試。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/tY2CcDr.webp&#34;
  alt=&#34;&#34;width=&#34;1011&#34; height=&#34;693&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著該路徑就可以看見 .sikuli 資料夾。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/vWYj0Ou.webp&#34;
  alt=&#34;&#34;width=&#34;301&#34; height=&#34;208&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見裡面有我們剛剛的圖片和一個 py 檔，我們實際在 IDE 撰寫的腳本就是這個 py 檔。&lt;/p&gt;
&lt;p&gt;接著測試最簡單的 &lt;code&gt;click()&lt;/code&gt; 方法，我們嘗試讓他按下按鈕試試。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/s4TliuU.webp&#34;
  alt=&#34;&#34;width=&#34;1539&#34; height=&#34;932&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;click()&lt;/code&gt; 將目標按鈕包起來之後按下執行，些微的停頓後就可以看見滑鼠直接滑向目標並按下去囉！整體的流程相當簡單，只需要目標的截圖和採用需要的方法就可以完成一個簡單的腳本。&lt;/p&gt;
&lt;p&gt;一些比較常用到的指令如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;click&lt;/code&gt;：點擊&lt;/li&gt;
&lt;li&gt;&lt;code&gt;doubleClick&lt;/code&gt;：點兩下&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rightClick&lt;/code&gt;：點右鍵&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dragDrop&lt;/code&gt;：拖曳&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hover&lt;/code&gt;：懸浮&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wait&lt;/code&gt;：等到指定圖樣出現&lt;/li&gt;
&lt;li&gt;&lt;code&gt;waitVanish&lt;/code&gt;：等到指定圖樣消失&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exist&lt;/code&gt;：指定圖樣是否存在&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;：輸入文字&lt;/li&gt;
&lt;li&gt;&lt;code&gt;paste&lt;/code&gt;：貼上文字&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，也能使用上方的工具列來圈選範圍，例如 &lt;code&gt;Show&lt;/code&gt; 就能顯示指令將抓取的位置，藉此校正腳本內容。大多把滑鼠移上去會有說明，這邊補充一下，感謝&lt;a href=&#34;https://qiita.com/mima_ita/items/8f653042ac9140e5023f&#34;&gt;這篇&lt;/a&gt;的示範：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Region&lt;/code&gt;：限制尋找範圍的區域&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Location&lt;/code&gt;：指定座標&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Offset&lt;/code&gt;：偏移量，平移找東西的時候好用，可參見&lt;a href=&#34;https://answers.launchpad.net/sikuli/+question/446476&#34;&gt;這篇&lt;/a&gt;的「Offset」&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Show&lt;/code&gt;：顯示該指令在螢幕上抓到的區域，可參見&lt;a href=&#34;http://wyj-learning.blogspot.com/2018/06/sikuli_30.html&#34;&gt;這篇&lt;/a&gt;的「Show &amp;amp; Show in」&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ShowIn&lt;/code&gt;：有時候 Show 會抓到多個符合條件的區域而選了第一個，但那又不是我們要的目標時，用 ShowIn 就能指定該在螢幕上尋找目標的區域&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些指令的組合已經足夠完成大部分的日常工作了，但其實 Sikulix 的潛力仍然還有許多，參考資料將附於本段末。&lt;/p&gt;
&lt;p&gt;我之所以體會到其強大處，是在前陣子在幫同學安裝 Python 環境時，就遇到需要說明「如何加入環境變數」的場合。像這種時候，我們就可以做一個自動打開環境變數設定頁面的腳本。（備註：系統操作需要用系統管理員身分執行 Sikulix 才能執行）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hpZ48n3.webp&#34;
  alt=&#34;&#34;width=&#34;467&#34; height=&#34;434&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;之後就可以丟給強者我同學讓他跑一次省得說明鬼撞牆。平常都丟影片，偶偶丟腳本也挺有工程師的感覺，哈。&lt;/p&gt;
&lt;p&gt;關於可以使用的方法和更深的進階操作，請參閱 &lt;a href=&#34;https://blog.csdn.net/sinat_27980131/article/details/51684001&#34;&gt;sikuli入门到进阶&lt;/a&gt;、&lt;a href=&#34;http://wyj-learning.blogspot.com/2018/06/sikuli_30.html&#34;&gt;半工室：Sikuli 使用 - 重點教學&lt;/a&gt; 以及 &lt;a href=&#34;https://sikulix-2014.readthedocs.io/en/latest/toc.html&#34;&gt;SikuliX 官方文件&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;關於一些進一步會遇到的問題，例如怎麼傳值進來腳本，請參閱&lt;a href=&#34;https://ypwalter.blogspot.com/2018/06/sikuli-sikulix.html?view=classic&#34;&gt;【測試】圖形化的自動測試 Sikuli / SikuliX 的相關技巧&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;心得&#34;&gt;心得&lt;/h2&gt;
&lt;p&gt;之前在看自動化操作的時候，關於網頁這種能看見原始碼找到操作點的可以直接使用 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium&#34;&gt;Selenium&lt;/a&gt;，而像 APP 也可以用工具抓到 Layout 去執行 Appium。然而像操作 Windows 或是一些應用程式等等就無法仰賴前兩者，這時候就是 Sikulix 出場的時候了。&lt;/p&gt;
&lt;p&gt;因為 Sikulix 是使用辨識圖像的方式進行的，因此只要有圖形化介面的東西都難不倒他，可以說相當實用，尤其是像我偶而會重灌的，就可以把每次會需要經歷的系統經歷錄成腳本，縮短每次環境佈署的時間，像是 &lt;a href=&#34;https://www.youtube.com/watch?v=FxDOlhysFcM&#34;&gt;2010 年 Sikuli 的示範影片&lt;/a&gt; 就是以設定 IP 為範例去操作呢。&lt;/p&gt;
&lt;p&gt;實際想一想，能用到的地方真的很多呢，例如…&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gr9DgeP.webp&#34;
  alt=&#34;&#34;width=&#34;186&#34; height=&#34;119&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「你的遊戲怎麼自己在動？」&lt;/p&gt;
&lt;p&gt;「這、這是為了學術用途！」&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;順便一提，原本的操作範例我是打算跑一次台鐵訂票等整個流程的操作介面，但是驗證碼的時候就卡關了，既然有了上次實作 Selenium 時看到 &lt;a href=&#34;https://www.largitdata.com/course/37/&#34;&gt;驗證碼的相關文章&lt;/a&gt;，也許這種時候就是機器學習出場的時機吧！這樣想著，希望之後能有機會可以嘗試看看，屆時再將記錄放上來。&lt;/p&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.tpisoftware.com/tpu/articleDetails/876&#34;&gt;Sikulix圖形辨識自動化測試開發工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ypwalter.blogspot.com/2018/06/sikuli-sikulix.html?view=classic&#34;&gt;【測試】圖形化的自動測試 Sikuli / SikuliX 的相關技巧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://wyj-learning.blogspot.com/2018/06/sikuli_30.html&#34;&gt;半工室：Sikuli 使用 - 重點教學&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/sinat_27980131/article/details/51684001&#34;&gt;sikuli入门到进阶&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://sportingmobile.blogspot.com/2016/06/sikuli.html&#34;&gt;相見恨晚的自動化測試開發工具 Sikuli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://answers.launchpad.net/sikuli/+question/446476&#34;&gt;Sikulix1.1.4を使って画面の自動操作をする&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>我要訂便當 (2): 用 Python &#43; Sqlite 儲存訂單</title>
      <link>https://igouist.github.io/post/2020/03/bandon-2-sqlite/</link>
      <pubDate>Sun, 15 Mar 2020 20:49:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/03/bandon-2-sqlite/</guid>
      <description>&lt;p&gt;前情回顧：上一集在 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt; 中，我們藉由自動化套件 Selenium 控制 Chrome 成功從訂便當網站裡取得訂單資訊了。但只能夠取得現在的訂單，和原本有新訂單的時候才通知的目標仍然有點差距，那麼，如何判斷有沒有新訂單呢？只要和上一次讀取時的訂單相比就能知道了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目標：將訂單儲存起來，判斷有沒有新訂單。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這一篇主要的做法主要參考自 &lt;a href=&#34;https://www.youtube.com/watch?v=pI3FDACJFAs&#34;&gt;大數軟體 - 如何透過 Line 發送最新一集的漫畫&lt;/a&gt; 中，關於如何判斷是否有最新一集漫畫的部分。這系列的影片步驟明瞭，說明直接，同時標題也很對我胃口（我就喜歡把工具拿來生活周遭玩的感覺），因此這邊也推薦一下，有興趣的可以去看看。&lt;/p&gt;
&lt;p&gt;回歸正題，這篇的第一部分就是要使用 Sqlite 將抓到的訂單儲存起來。Sqlite 顧名思義就是 SQL + Lite 的感覺，主打小巧輕便。它會將資料儲存在一個檔案中，並且支援精簡的 SQL 指令，可以說是相當方便。&lt;/p&gt;
&lt;p&gt;在 Python 要對 Sqlite 做操作主要是藉由 sqlite3 這個包，因此在接下來的步驟前，請先安裝這個包。關於 Sqlite3 的基本操作，可以參閱 &lt;a href=&#34;https://www.runoob.com/sqlite/sqlite-python.html&#34;&gt;菜鳥教程的 SQLite - Python&lt;/a&gt; 教學。&lt;/p&gt;
&lt;p&gt;基本上和一般資料庫的操作邏輯並無太多差異，主要也是以 &lt;code&gt;sqlite3.connect&lt;/code&gt; 先連線到資料庫中，再使用如 &lt;code&gt;connection.execute&lt;/code&gt; 等的執行語法來進行操作。同時連線之後也可以使用其他資料框架提供的儲存和讀取等方法來處理，上方的菜鳥教程已有完整的教學流程，因此這邊就不多做說明，以下將會直接寫出操作資料庫動作的函式。&lt;/p&gt;
&lt;h2 id=&#34;儲存與讀取訂單&#34;&gt;儲存與讀取訂單&lt;/h2&gt;
&lt;p&gt;由於我們的情境相對簡單，只需要儲存和讀取。因此我們直接建立這兩個方法來操作。且因為我們在上一集讀回來的資料已經轉成 DataFrame，因此可以直接調用相關的方法進行操作。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;saveToDb&lt;/span&gt;(data, dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 將資料表存放到 sqlLite 資料庫 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        data&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_sql(tablename, con &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db, if_exists &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;replace&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readFromDb&lt;/span&gt;(dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 從資料庫取出上次資料表 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; pandas&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read_sql_query(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SELECT * FROM &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{tablename}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(tablename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tablename), db)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;delDataInDb&lt;/span&gt;(dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 將指定資料表的資料刪除 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cursor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cursor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cursor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;execute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SELECT * FROM &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{tablename}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(tablename&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tablename))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;commit()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cursor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;另外也要考慮到可能是第一次連接到資料庫，若是指定的資料表不存在可能會報錯，因此加上一個檢查有沒有資料表的方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IsDbTableExist&lt;/span&gt;(dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 確認是否有資料表 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cursor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        c&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;execute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT count(*) FROM sqlite_master WHERE type=&amp;#39;table&amp;#39; AND name=&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{tablename}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(tablename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tablename))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; c&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetchone()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; : 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這些小零件完成之後，就可以把它們加到主要的流程裡。最簡單的順序應該是這樣的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- 取得網路訂單資訊
- 和資料庫的訂單資訊比對
    - 如果有差異，且訂單變多代表有新訂單！ → 後續的通知等動作
    - 如果沒有差異，或是訂單變少了，代表沒有新訂單。 → 按兵不動
- 將新的資料存回資料庫
- 結束
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照這個流程加入之前的主程式後，就會變得如下（全程式將附於文末）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://dinbendon.net/do/login&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dbname &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;order.sqlite&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tablename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;bandon&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    order &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch_bandon(url)  &lt;span style=&#34;color:#75715e&#34;&gt;# 連線到便當網取得訂單資訊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; order &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; len(order) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;目前沒有進行中的訂單&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 如果資料庫沒資料表（＝第一次執行）先存一次建個環境&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; IsDbTableExist(dbname, tablename) &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;資料庫無過往訂單，即將儲存目前訂單並退出&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        saveToDb(order, dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print_order(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    oldOrder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; readFromDb(dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    orderList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(order[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    oldOrderList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(oldOrder[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#print_order(order)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    delDataInDb(dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    saveToDb(order, dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 如果上次訂單是空的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; oldOrder &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; len(oldOrder) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;資料庫無過往訂單，即將儲存目前訂單並退出&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; orderList &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; oldOrderList &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; len(orderList) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; len(oldOrderList):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;訂單無變動，程式即將退出&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;偵測到訂單變動！&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 做點通知的事&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;尚未實作任何通知，程式即將終止…&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print_order(order)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;完成之後，接著進行測試，首先用顧客帳號登入便當網，確認一下目前的訂單內容&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/R1KEk0P.webp&#34;
  alt=&#34;&#34;width=&#34;618&#34; height=&#34;367&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著先執行過一次&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/NvyBzsF.webp&#34;
  alt=&#34;&#34;width=&#34;560&#34; height=&#34;204&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;




&lt;img
  src=&#34;https://image.igouist.net/d9CS1eH.webp&#34;
  alt=&#34;&#34;width=&#34;244&#34; height=&#34;150&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見有確實抓到訂單部分，並且在 py 檔所在的位置也多出了一個 sqlite 檔案存放我們的資料。&lt;/p&gt;
&lt;p&gt;那麼接著直接再執行一次，確認是不是會確認資料一致&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/phs8YTv.webp&#34;
  alt=&#34;&#34;width=&#34;294&#34; height=&#34;143&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著登入便當網，自己發起一個團購試試&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VIvjywx.webp&#34;
  alt=&#34;&#34;width=&#34;607&#34; height=&#34;384&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著我們再執行試試能不能告訴我們有差異&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GpQkhyv.webp&#34;
  alt=&#34;&#34;width=&#34;424&#34; height=&#34;278&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見確實有抓到差異了！&lt;/p&gt;
&lt;p&gt;接著要實作任何通知都相當簡單了，只要將和之前不一樣的部分取出就可以取得新增的訂單，或是可以記錄每個訂單的開始和結束時間做管理，可以說能做的事相當多。&lt;/p&gt;
&lt;p&gt;屆此已經完成了預定將訂單做儲存和偵測有沒有變動的部分，故這篇就先到這裡打住。原本考慮到這部分相較之下篇幅比較短，是不是該跟下一段落合併成一篇，但考量之後還是作罷。&lt;/p&gt;
&lt;p&gt;畢竟大多時候也是做給自己往後參考用的，希望能把過程和看過的資料記錄下來，將來需要的時候可以自己抄自己，因此還是決定將不同套件的部份作切割，也是在這時候確定了這系列將會有四篇。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;下一篇&lt;/a&gt; 將處理通知的部分，用到的技巧也在上面稍微提到過囉！那麼就下週再見～&lt;/p&gt;
&lt;h2 id=&#34;補充怎麼看-sqlite-裡的資料&#34;&gt;補充：怎麼看 Sqlite 裡的資料？&lt;/h2&gt;
&lt;p&gt;紀錄一下看 Sqlite 檔案裡資料的方法。&lt;/p&gt;
&lt;p&gt;由於 Sqlite 主打輕便可攜，因此平常的需求大多也是看資料而已。使用 &lt;a href=&#34;https://sqlitebrowser.org/&#34;&gt;DB Browser for SQLite&lt;/a&gt; 就足夠了&lt;/p&gt;
&lt;p&gt;不過我個人是比較偏好直接從 VS Code 裡就打開看，這時候就需要安裝擴充套件。&lt;/p&gt;
&lt;p&gt;我是採用 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=alexcvzz.vscode-sqlite&#34;&gt;Sqlite 這個套件&lt;/a&gt;，該頁面就已經有操作示範了，使用相當簡單。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qjHRK9z.webp&#34;
  alt=&#34;&#34;width=&#34;1561&#34; height=&#34;586&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;只要先用 Ctrl + Shift + P 叫出命令提示區，輸入 Sqlite 之後，開啟資料庫就可以用簡單的操作來看內容或執行 SQL 指令，這邊強烈推薦。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Ces9nxj.webp&#34;
  alt=&#34;&#34;width=&#34;944&#34; height=&#34;406&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;我要訂便當系列&#34;&gt;我要訂便當系列&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;我要訂便當(2) —— 用 Python + Sqlite 儲存訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;我要訂便當(3) —— 用 Python + Line Notify 傳送通知&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;我要訂便當(4) —— 將 Python 腳本部署上 Heroku&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-5-heroku-debug/&#34;&gt;我要訂便當(5) —— Heroku 填坑小記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=pI3FDACJFAs&#34;&gt;大數軟體 - 如何透過 Line 發送最新一集的漫畫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.runoob.com/sqlite/sqlite-python.html&#34;&gt;菜鳥教程 - SQLite Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.aaronlife.com/v1/teaching/android_sqlite.html&#34;&gt;SQLite 資料庫介紹 - Aaron Ho&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10204523&#34;&gt;DAY22 - 搞懂如何導入sqlite - iT邦幫忙&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://yhhuang1966.blogspot.com/2018/04/python-sqlite_28.html&#34;&gt;Python 學習筆記 : 資料庫存取測試 (一) SQLite - 小狐狸事務所&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;附錄目前程式碼&#34;&gt;附錄：目前程式碼&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; selenium &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; webdriver
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pandas
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; sqlite3 &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; lite
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 自動檢查團購便當網&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://dinbendon.net/do/login&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dbname &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;order.sqlite&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tablename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;bandon&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    order &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch_bandon(url)  &lt;span style=&#34;color:#75715e&#34;&gt;# 連線到便當網取得訂單資訊&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; order &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; len(order) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;目前沒有進行中的訂單&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 如果資料庫沒資料表（＝第一次執行）先存一次建個環境&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; IsDbTableExist(dbname, tablename) &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;資料庫無過往訂單，即將儲存目前訂單並退出&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        saveToDb(order, dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print_order(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    oldOrder &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; readFromDb(dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    orderList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(order[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    oldOrderList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(oldOrder[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#print_order(order)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    delDataInDb(dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    saveToDb(order, dbname, tablename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 如果上次訂單是空的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; oldOrder &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; len(oldOrder) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;資料庫無過往訂單，即將儲存目前訂單並退出&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; orderList &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; oldOrderList &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; len(orderList) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; len(oldOrderList):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;訂單無變動，程式即將退出&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;偵測到訂單變動！&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 做點通知的事&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;尚未實作任何通知，程式即將終止…&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print_order(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch_bandon&lt;/span&gt;(url, username&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;guest&amp;#34;&lt;/span&gt;, password&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;guest&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 開啟瀏覽器並連線到便當網取得資料 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; webdriver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ChromeOptions()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#options.add_argument(&amp;#39;headless&amp;#39;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; webdriver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Chrome(options&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;options)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(url)  &lt;span style=&#34;color:#75715e&#34;&gt;# 連線到訂便當頁面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;# 演一下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 輸入帳密&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(username)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(password)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 輸入驗證碼&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ques &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_class_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alignRight&amp;#34;&lt;/span&gt;)[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;findall(&lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\d+\.?\d*&amp;#34;&lt;/span&gt;, ques)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    answer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(temp[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; int(temp[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;result&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(answer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 提交表單&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;click()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#time.sleep(1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 取出訂單表格列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rows &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_css_selector(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div#inProgressBox&amp;gt;table&amp;gt;tbody&amp;gt;tr&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(rows) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; list()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bandons &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [list(map(getText, row&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_css_selector(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;td&amp;gt;div&amp;gt;a&amp;gt;span&amp;#34;&lt;/span&gt;))) &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; row &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; rows]  &lt;span style=&#34;color:#75715e&#34;&gt;# 取出每一列資料的文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tableHeader &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;人數&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;發起人&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bandons_df &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pandas&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;DataFrame(bandons, columns&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tableHeader)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; bandons_df
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;print_order&lt;/span&gt;(data):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;列印訂單資料，看起來整齊一點&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; index, row &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; data&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;iterrows():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; row &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{hcount:&amp;gt;4s}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;) &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{orderer}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{order:&amp;lt;40s}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                orderer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; str(row[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;發起人&amp;#39;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                order &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; str(row[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                hcount &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; str(row[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;人數&amp;#39;&lt;/span&gt;])))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getText&lt;/span&gt;(x):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; x&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;saveToDb&lt;/span&gt;(data, dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 將資料表存放到 sqlLite 資料庫 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        data&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_sql(tablename, con&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;db, if_exists&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;replace&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readFromDb&lt;/span&gt;(dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 從資料庫取出上次資料表 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; pandas&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read_sql_query(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SELECT * FROM &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{tablename}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(tablename&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tablename), db)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;delDataInDb&lt;/span&gt;(dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 將指定資料表的資料刪除 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cursor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cursor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cursor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;execute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SELECT * FROM &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{tablename}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(tablename&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tablename))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;commit()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cursor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IsDbTableExist&lt;/span&gt;(dbname, tablename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 確認是否有資料表 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; lite&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(dbname) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; db:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cursor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        c&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;execute(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT count(*) FROM sqlite_master WHERE type=&amp;#39;table&amp;#39; AND name=&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{tablename}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(tablename&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tablename))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; c&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fetchone()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>我要訂便當 (1): 用 Python &#43; Selenium 控制瀏覽器取得訂單</title>
      <link>https://igouist.github.io/post/2020/03/bandon-1-selenium/</link>
      <pubDate>Sun, 08 Mar 2020 18:47:23 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2020/03/bandon-1-selenium/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;前言：這是參加&lt;a href=&#34;https://www.hexschool.com/2019/11/14/2019-11-14-w3Hexschool-2020-challenge/&#34;&gt;六角鼠年全馬&lt;/a&gt;的第一篇，主要是希望能夠養成寫部落格的習慣。由於我本身並沒有主要技能，因此這次參賽文章會以我最近玩的玩具、使用的套件或是遇到的問題做紀錄。希望能夠派上用場。&lt;/p&gt;
&lt;p&gt;目標：使用 &lt;strong&gt;Python&lt;/strong&gt; 及 &lt;strong&gt;Selenium&lt;/strong&gt; 連線到訂便當網站，自動輸入帳號密碼登入後，取回網站上的訂單資訊&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2020.12 更新: 由於訂便當網站改版，所以程式碼已經不能照抄了。但有興趣的朋友還是能自己摸索做點變動，也能夠照常進行喔，加油～&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2024.10 更新: Line Notify 將於 2025 年 3 月停止服務（&lt;a href=&#34;https://notify-bot.line.me/closing-announce&#34;&gt;LINE Notify 結束服務公告&lt;/a&gt;），有看到這篇的朋朋請選擇一組新的通知服務來串吧 QQ&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;最近在公司的時候有個莫大的煩惱，就是關於辦公室團購這回事兒。現在待著的公司主要是從 &lt;a href=&#34;https://dinbendon.net/&#34;&gt;Dinbendon&lt;/a&gt; 這套系統來揪團購，舉凡品客、火鍋等都在上面訂過，據我觀察最受歡迎出現最多次的當屬雞排了。煩惱就在於，每次都會錯過雞排的團購，光在辦公室聞著四面八方傳來的雞排香味，就令人無法忍受！因此趁著這個機會，來嘗試能不能像之前的 &lt;a href=&#34;https://igouist.github.io/post/2019/12/ptt-crawler-and-listener/&#34;&gt;PTT&lt;/a&gt; 一樣來弄出一個通知，順便玩玩最近看到的工具。這系列的文章會分成多個部分，主要是以使用的工具來分集。&lt;/p&gt;
&lt;p&gt;由於在從團購網取得訂單的過程中需要跟網頁進行互動，因此這次要使用的工具是 &lt;strong&gt;&lt;a href=&#34;https://www.selenium.dev/&#34;&gt;Selenium&lt;/a&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Selenium 是一個對網頁做自動化測試的工具&lt;/strong&gt;，但我個人比較常在爬蟲的時候用到XD。它能夠經由腳本或錄製的方式對瀏覽器進行操作，並且也支援相當多語言可以使用，例如我同事便使用 C# 和 Hangfire 來完成訂便當的目標（對，這麼無聊的人不只我一個），而我則用相對比較熟悉的 Python 來實作。&lt;/p&gt;
&lt;p&gt;關於本篇主要的操作和步驟，主要參考 &lt;a href=&#34;https://medium.com/@NorthBei/%E5%9C%A8windows%E4%B8%8A%E5%AE%89%E8%A3%9Dpython-selenium-%E7%B0%A1%E6%98%93%E6%95%99%E5%AD%B8-eade1cd2d12d&#34;&gt;在 Windows 上安裝 Python &amp;amp; Selenium 簡易教學&lt;/a&gt; 這篇文章，在此感謝；而各語言的語法等等，可以翻閱 &lt;a href=&#34;https://selenium-python-zh.readthedocs.io/en/latest/index.html&#34;&gt;教學文檔&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&#34;準備工作&#34;&gt;準備工作&lt;/h2&gt;
&lt;p&gt;開始寫腳本之前，確保 Python 已經安裝完畢，並且先下載好 &lt;a href=&#34;https://pypi.org/project/selenium/&#34;&gt;Selenium 套件包&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;另外 &lt;strong&gt;Selenium 是使用各個 Web Driver 來對瀏覽器做操作的&lt;/strong&gt;，因此這邊也需要先下載 Chrome 的 Driver 來使用。進入 &lt;a href=&#34;http://chromedriver.chromium.org/downloads&#34;&gt;ChromeDriver 的下載頁面&lt;/a&gt; ，通常挑選最新版的下載，如果 Chrome 版本有需求再選擇對應的版本即可。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/z0BGk4z.webp&#34;
  alt=&#34;&#34;width=&#34;1082&#34; height=&#34;758&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2020.04.02 補充：關於&lt;strong&gt;其他瀏覽器的 Driver&lt;/strong&gt;，可以參考 iT 邦幫忙的 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10229959/&#34;&gt;鼠年全馬鐵人挑戰 WEEK 06：Selenium 自動化測試工具&lt;/a&gt; 這篇，裡面有詳細的介紹以及各瀏覽器的 Driver 下載整理。&lt;/p&gt;
&lt;p&gt;此外除了用腳本控制 Driver 的用法以外，&lt;strong&gt;Selenium 也提供了 IDE&lt;/strong&gt; 可以直接使用，需要先安裝 Chrome 和 Firefox 的擴充套件，詳情可以參閱同系列的 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10230427&#34;&gt;鼠年全馬鐵人挑戰 WEEK 07：Selenium IDE&lt;/a&gt; 內有使用說明。&lt;/p&gt;
&lt;p&gt;發完之後才看到這個系列，對測試的種類和 Selenium 的操作說明得清楚多了，值得推薦，故在此補上。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;下載完解壓縮應該會有一個 chromedriver.exe 檔案，這個檔案的用法有兩種&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;放置於 Python.exe 所在的位置，即當初的安裝位置，如此所有的腳本都可以使用&lt;/li&gt;
&lt;li&gt;放置於現在專案的 py 檔同一個資料夾，就只有這個資料夾中的腳本可以使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;當然前者放一次就都可以用比較方便，不過這邊只打算迅速地讓這個腳本動起來，因此可以直接放置在等等要寫 Python 檔的資料夾就可以了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/7m1RKc8.webp&#34;
  alt=&#34;&#34;width=&#34;352&#34; height=&#34;109&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;那麼準備工作完成之後，就可以開始來寫 Code 讓它動起來囉！&lt;/p&gt;
&lt;h2 id=&#34;取得訂單&#34;&gt;取得訂單&lt;/h2&gt;
&lt;p&gt;首先測試是否能夠順利連線上便當網，這邊先撰寫最簡單的連線。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; selenium &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; webdriver
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://dinbendon.net/do/login&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; webdriver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Chrome()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(url)  &lt;span style=&#34;color:#75715e&#34;&gt;# 連線到訂便當頁面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;執行之後應該就能看到 Chrome 自動開啟連線到指定的網頁，同時也可以注意到 Chrome 上有標明「正在受到自動測試軟體控制」&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/741auju.webp&#34;
  alt=&#34;&#34;width=&#34;927&#34; height=&#34;622&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著想要看到訂單內容，還必須要輸入帳號密碼和驗證碼才行，這也就是前面提到的需要互動的部分。先使用 F12 的使用者工具觀察欄位的名稱，以利後續 Selenium 的抓取 &lt;del&gt;，爬蟲的基本就在於拆人家的房子&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3SWOJtJ.webp&#34;
  alt=&#34;&#34;width=&#34;986&#34; height=&#34;416&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;確認名稱之後就可以添加指令，讓 Selenium 幫我們輸入看看。這邊要注意我們加上了 &lt;code&gt;sleep()&lt;/code&gt; 來暫停一下，因為在 Selenium 的操作之間，建議要加上些許延遲，避免畫面動作都還沒完成，指令就一股腦丟完了囧。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;# 演一下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;username &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;password &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 輸入帳密&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(username)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(password)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到它會自動幫我們輸入內容，看著帳密自己跳出來實在是相當療癒&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WvY9vco.webp&#34;
  alt=&#34;&#34;width=&#34;318&#34; height=&#34;316&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在這一步去抓取網頁上的元素時，可以看見我使用了 &lt;code&gt;driver.find_element_by_name&lt;/code&gt; 去按照網頁上 HTML 標籤的 name 去抓到目標的元件。這就是 &lt;strong&gt;Selenium 的定位器&lt;/strong&gt;，它提供了許多方法去取得目標元件，例如 Id、Name 等等。&lt;/p&gt;
&lt;p&gt;關於定位器的操作可以參閱 &lt;a href=&#34;https://matthung0807.blogspot.com/2017/12/selenium-html-element-locator.html&#34;&gt;Selenium HTML element locator 定位器&lt;/a&gt; 以及 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10194253&#34;&gt;Selenium webdriver 定位物件方法比較 xpath v.s. css selector&lt;/a&gt; 這兩篇。接下來的介紹會以使用為主。&lt;/p&gt;
&lt;p&gt;回到我們的便當網，這網頁的友善就在於它的驗證碼是顯示數字讓你計算，每次的變化只有中間的「+」可能會變成「加」和全形的「＋」。但這並不妨礙我們去把它的值剝取出來。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 輸入驗證碼&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ques &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_class_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alignRight&amp;#34;&lt;/span&gt;)[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text &lt;span style=&#34;color:#75715e&#34;&gt;# 有點強硬地拿到整串問題&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;temp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;findall(&lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\d+\.?\d*&amp;#34;&lt;/span&gt;, ques) &lt;span style=&#34;color:#75715e&#34;&gt;# 用正規表達式把數字取出&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(temp[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(temp[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; b
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;result&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(c)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看見它自動幫我們輸入了計算結果&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/J4IXXCx.webp&#34;
  alt=&#34;&#34;width=&#34;249&#34; height=&#34;157&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;題外話：如果遇到麻煩點的驗證碼怎麼辦？&lt;/p&gt;
&lt;p&gt;可以先用&lt;a href=&#34;https://www.youtube.com/watch?v=hF-dJj559ug&#34;&gt;大數軟體 - 如何使用 Selenium 抓取驗證碼?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;再試試看&lt;a href=&#34;https://www.largitdata.com/course/37/&#34;&gt;大數學堂 - 如何透過 OpenCV 破解台灣證券交易所買賣日報表的驗證碼(Captcha)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;也許能有效，先記錄下來。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;接著就可以測試是否能夠登入了，將帳號密碼設定為測試用的訪客帳號 guest，並在指令最後添加按下按鈕的動作&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 提交表單&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;click() 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到這一步已經順利登入，並且可以看到訂單列表了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PQyd9FW.webp&#34;
  alt=&#34;&#34;width=&#34;922&#34; height=&#34;738&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;整理及包裝&#34;&gt;整理及包裝&lt;/h2&gt;
&lt;p&gt;接著流程一如前部分，觀察網頁結構並且將目標取出。&lt;/p&gt;
&lt;p&gt;這邊先將左半部分的 Table 拿出來，接著針對表格的每一列取出該元素之後取文字。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 取出訂單表格列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rows &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_css_selector(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div#inProgressBox&amp;gt;table&amp;gt;tbody&amp;gt;tr&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(rows) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; list()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 取出每一列資料的文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bandons &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [list(map(getText, row&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_css_selector(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;td&amp;gt;div&amp;gt;a&amp;gt;span&amp;#34;&lt;/span&gt;))) &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; row &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; rows]  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 做成一張表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tableHeader &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;人數&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;發起人&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bandons_df &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pandas&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;DataFrame(bandons, columns&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tableHeader)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;目前為止整體程式碼如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; selenium &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; webdriver
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pandas
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 自動檢查團購便當網&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://dinbendon.net/do/login&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    order &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch_bandon(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print_order(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch_bandon&lt;/span&gt;(url, username&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;guest&amp;#34;&lt;/span&gt;, password&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;guest&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39; 開啟瀏覽器並連線到便當網取得資料 &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; webdriver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Chrome()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(url)  &lt;span style=&#34;color:#75715e&#34;&gt;# 連線到訂便當頁面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;# 演一下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 輸入帳密&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(username)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(password)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 輸入驗證碼&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ques &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_class_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alignRight&amp;#34;&lt;/span&gt;)[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    temp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;findall(&lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\d+\.?\d*&amp;#34;&lt;/span&gt;, ques)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    answer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(temp[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; int(temp[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;result&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_keys(answer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 提交表單&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_element_by_name(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;click()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 取出訂單表格列&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rows &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_css_selector(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;div#inProgressBox&amp;gt;table&amp;gt;tbody&amp;gt;tr&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(rows) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; list()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 取出每一列資料的文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bandons &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [list(map(getText, row&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find_elements_by_css_selector(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;td&amp;gt;div&amp;gt;a&amp;gt;span&amp;#34;&lt;/span&gt;))) &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; row &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; rows]  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 做成一張表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tableHeader &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;人數&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;發起人&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bandons_df &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pandas&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;DataFrame(bandons, columns&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tableHeader)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; bandons_df
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;print_order&lt;/span&gt;(data):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;列印訂單資料，看起來整齊一點&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; index, row &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; data&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;iterrows():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; row &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{hcount:&amp;gt;4s}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;) &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{orderer}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{order:&amp;lt;40s}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                orderer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; str(row[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;發起人&amp;#39;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                order &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; str(row[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;目標&amp;#39;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                hcount &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; str(row[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;人數&amp;#39;&lt;/span&gt;])))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getText&lt;/span&gt;(x):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; x&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;抓回來的樣子如下



&lt;img
  src=&#34;https://image.igouist.net/jp1lQlr.webp&#34;
  alt=&#34;&#34;width=&#34;1042&#34; height=&#34;702&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;另外每次執行的時候都還會有瀏覽器跳出來操作，但我們在這邊已經確認可以成功取回資料了，因此瀏覽器的顯示也不是那麼必要。&lt;/p&gt;
&lt;p&gt;這邊就可以考慮加上無頭模式讓瀏覽器不要顯示，而是在背景執行。只需要在一開始宣告瀏覽器的部分加上選項，就可以不要跳視窗囉。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; webdriver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ChromeOptions()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;headless&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; webdriver&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Chrome(options&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;options)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到此為止我們已經成功控制瀏覽器幫我們打開網頁，填帳號密碼登入，也取得了想要的訂單列表內容，完成了訂便當野心的第一步！&lt;/p&gt;
&lt;p&gt;然而，接著還有相當多的部分必須處理。如何判斷有沒有新訂單？又要怎麼通知我有新訂單呢？&lt;/p&gt;
&lt;p&gt;欲知後續如何，且待 &lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;下回&lt;/a&gt; 分曉！&lt;/p&gt;
&lt;h2 id=&#34;我要訂便當系列&#34;&gt;我要訂便當系列&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-1-selenium/&#34;&gt;我要訂便當(1) —— 用 Python + Selenium 控制瀏覽器取得訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/03/bandon-2-sqlite/&#34;&gt;我要訂便當(2) —— 用 Python + Sqlite 儲存訂單&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/04/bandon-3-line-notify/&#34;&gt;我要訂便當(3) —— 用 Python + Line Notify 傳送通知&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-4-heroku/&#34;&gt;我要訂便當(4) —— 將 Python 腳本部署上 Heroku&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://igouist.github.io/post/2020/05/bandon-5-heroku-debug/&#34;&gt;我要訂便當(5) —— Heroku 填坑小記&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;參考資料&#34;&gt;參考資料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@NorthBei/%E5%9C%A8windows%E4%B8%8A%E5%AE%89%E8%A3%9Dpython-selenium-%E7%B0%A1%E6%98%93%E6%95%99%E5%AD%B8-eade1cd2d12d&#34;&gt;在Windows上安裝Python &amp;amp; Selenium + 簡易教學&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://selenium-python-zh.readthedocs.io/en/latest/index.html&#34;&gt;Selenium with Python中文翻译文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@bob800530/selenium-1-%E9%96%8B%E5%95%9Fchrome%E7%80%8F%E8%A6%BD%E5%99%A8-21448980dff9&#34;&gt;運用 Selenium 開啟 Chrome 瀏覽器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10229959/&#34;&gt;鼠年全馬鐵人挑戰 WEEK 06：Selenium 自動化測試工具&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10230427&#34;&gt;鼠年全馬鐵人挑戰 WEEK 07：Selenium IDE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://matthung0807.blogspot.com/2017/12/selenium-html-element-locator.html&#34;&gt;Selenium HTML element locator 定位器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10194253&#34;&gt;Selenium webdriver 定位物件方法比較 xpath v.s. css selector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=grZi9j4HKvc&#34;&gt;大數軟體 - 如何使用 Selenium 自動下載漫畫?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=hF-dJj559ug&#34;&gt;大數軟體 - 如何使用 Selenium 抓取驗證碼?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.largitdata.com/course/37/&#34;&gt;大數學堂 - 如何透過 OpenCV 破解台灣證券交易所買賣日報表的驗證碼(Captcha)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>Python: 用爬蟲在 PTT 上監聽關鍵字並寄通知信</title>
      <link>https://igouist.github.io/post/2019/12/ptt-crawler-and-listener/</link>
      <pubDate>Mon, 09 Dec 2019 10:05:00 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2019/12/ptt-crawler-and-listener/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;前陣子很想跟 Netflix 的團購，三不五時就上 PTT 看一下團購板，但看到的時候大多已經截止，還有填單填到一半發現已經收滿的，氣得七竅生煙。故嘗試寫了一個通知，在這邊記錄下來。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目標：當團購板上新發了一篇 Netflix 的文，馬上寄信告訴我。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;為了這個目標，我們基本上需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用爬蟲取得團購板的文章標題&lt;/li&gt;
&lt;li&gt;能夠寄信（使用 Gmail）&lt;/li&gt;
&lt;li&gt;持續監視，也就是重複執行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;使用-requests-爬蟲&#34;&gt;使用 requests 爬蟲&lt;/h2&gt;
&lt;p&gt;所謂的爬蟲就是傳送 HTTP 去瀏覽網頁，並把網頁的內容（像 HTML 等）打包回來分析使用。&lt;/p&gt;
&lt;p&gt;這部分，我是參考這篇 &lt;a href=&#34;https://github.com/leVirve/CrawlerTutorial&#34;&gt;爬蟲極簡教學&lt;/a&gt; 來寫的，主要是使用 &lt;strong&gt;requests&lt;/strong&gt; 來取得 HTML，再使用 &lt;strong&gt;requests_html&lt;/strong&gt; 解析，裡面的說明非常詳盡。&lt;/p&gt;
&lt;p&gt;除了這兩個，其他常用到的工具還有：解析 HTML 架構的 &lt;strong&gt;Beautiful Soup&lt;/strong&gt;（&lt;a href=&#34;https://blog.gtwang.org/programming/python-beautiful-soup-module-scrape-web-pages-tutorial/&#34;&gt;教學&lt;/a&gt;）、模擬瀏覽器的 &lt;strong&gt;Selenium&lt;/strong&gt; 等。一些常用的工具可以參考這篇 &lt;a href=&#34;https://blog.v123582.tw/2018/09/03/%E5%AD%B8%E7%BF%92-Python-%E7%88%AC%E8%9F%B2%E7%9A%84%E6%9C%80%E4%BD%B3%E8%B7%AF%E5%BE%91/&#34;&gt;爬蟲的工具鍊&lt;/a&gt; 的介紹。&lt;/p&gt;
&lt;p&gt;接著開始跟隨教學走吧。首先確認目標網站的 HTML，可以發現在 PTT 上每一篇文章都是放在 r-ent 的 div 裡的。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/DAbF4NX.webp&#34;
  alt=&#34;&#34;width=&#34;536&#34; height=&#34;329&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;首先就是要先到目標網頁將 HTML 打包回來。此外，PTT 再初次進入時，會跳出視窗詢問是否已經滿十八歲，因此這邊也必須要先處理。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; requests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(url):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;傳入網址，向 PTT 回答已經滿 18 歲，回傳網頁內容&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(url, cookies&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;over18&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1&amp;#39;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; response
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下來利用 requests_html 將 div.r-ent 拆出來，也可以用其他的 HTML 解析模組（例如比較熟悉用 Beautiful Soup 之類的時候）來替代：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; requests_html &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; HTML
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_article_entries&lt;/span&gt;(doc):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;傳入網頁內容，利用 requests_html 取出 div.r-ent 的元素內容並回傳&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    html &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HTML( html &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; doc )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post_entries &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; html&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.r-ent&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; post_entries
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最後將拆出來的資料做成字典，方便之後操作。但這邊要注意，被刪除的文章會缺少作者和連結，直接拿會產生錯誤，因此必須要篩掉：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_article_meta&lt;/span&gt;(entry):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;將 r-ent 元素的內容格式化成 dict 再回傳&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    meta &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;: entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.title&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;push&amp;#39;&lt;/span&gt;: entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.nrec&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;date&amp;#39;&lt;/span&gt;: entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.date&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 正常的文章可以取得作者和連結&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;author&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.author&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.title &amp;gt; a&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;attrs[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;href&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AttributeError&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 被刪除的文章我們就不要了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;author&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[Deleted]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[Deleted]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; meta
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到這邊應該能取得首頁上目前的文章了。由於我們做的是監聽團購的目標是不是有人發文，因此我們並不需要再進連結取得內文、或是翻頁繼續爬等更複雜的操作，只需要第一頁的資料就足夠了。（如果想要翻頁，例如說需要爬前一百頁的時候，上面的爬蟲極簡教學有使用抓翻頁按鈕的連結來達成的做法可以參考。）&lt;/p&gt;
&lt;h2 id=&#34;使用-smtplib-寄信&#34;&gt;使用 smtplib 寄信&lt;/h2&gt;
&lt;p&gt;寄信部分利用 smtplib 來做 SMTP（簡單郵件傳輸）。使用方式相當簡單，可以參考 &lt;a href=&#34;http://www.runoob.com/python/python-email.html&#34;&gt;菜鳥教程的說明&lt;/a&gt; 。這邊為了方便，直接使用 Gmail 來寄件。&lt;em&gt;（註：要讓 Gmail 可以用這種程式登入的方法來寄信，需要先開啟允許安全性較低的應用程式設定）&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;send_mail_for_me&lt;/span&gt;(meta):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;利用 Gmail 的服務寄發通知信&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    send_gmail_user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;寄送者@gmail.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    send_gmail_password &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;********&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rece_gmail_user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;接收者@gmail.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MIMEText(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;您所追蹤的 &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; KEYWORD &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; 已經出現在板上！&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; 文章：&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;https://www.ptt.cc&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Subject&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;PTT 監聽通知信&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;From&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; send_gmail_user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;To&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rece_gmail_user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 使用 SSL 加密 連線到 gmail 提供的 smtp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; smtplib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SMTP_SSL(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;smtp.gmail.com&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;465&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ehlo()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;login(send_gmail_user, send_gmail_password)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_message(msg)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;quit()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這邊目標是將爬到的那一項傳進來寄出去。可以先稍微測試，寄一點垃圾信試試看（找不到的時候記得找一下垃圾信件）。確定收得到之後就可以整合進去了。&lt;/p&gt;
&lt;h2 id=&#34;本體&#34;&gt;本體&lt;/h2&gt;
&lt;p&gt;接著將上面的兩個部分整合起來：爬蟲，然後如果爬到目標就寄信，沒爬到就準備重爬。這邊先放一個 flog 來準備之後判斷要不要繼續爬的部分。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ptt_alert&lt;/span&gt;(url, keyword):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; url &lt;span style=&#34;color:#75715e&#34;&gt;# 網址&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch(url) &lt;span style=&#34;color:#75715e&#34;&gt;# 取得網頁內容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post_entries &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parse_article_entries(resp&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text) &lt;span style=&#34;color:#75715e&#34;&gt;# 取得各列標題&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 連線成功，開始搜尋目標「&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;」&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;(t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(), KEYWORD))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; entry &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; post_entries:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parse_article_meta(entry)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 如果找到關鍵字，而且還沒截止，寄信通知我&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 記得先試著轉小寫，否則大小寫視作不同&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; keyword &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lower() &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;截止&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print_meta(meta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            send_mail_for_me(meta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 已發現監聽目標！通知信已寄出&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            flog &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 用來紀錄找到了沒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 沒找到的時候就正常顯示&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print_meta(meta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 搜尋完畢，並未發現目標。正在休眠 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; 毫秒並等待下一輪搜尋……&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;(t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(), SLEEPTIME))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著就是重複執行的部分。抱著嘗試的精神試過 Win內建的工作排程器、&lt;a href=&#34;https://zhuanlan.zhihu.com/p/46948464&#34;&gt;apscheduler&lt;/a&gt; 等，最後還是簡單最好，用傳統的 while 和 break 解決。另外，這邊用 time.sleep 做延遲，延遲的時間需要自己衡量一下。例如說團購板的發文速度並不算快，五分鐘爬一次已經差不多；但若是發文速度較快的板，則可能需要向 縮短每輪間隔時間 或是 一次爬比較多頁 這兩個方向去做處理。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 開始執行監聽&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ptt_alert(URL, KEYWORD) &lt;span style=&#34;color:#75715e&#34;&gt;# 開始執行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;  flog: &lt;span style=&#34;color:#75715e&#34;&gt;# 如果執行後有找到目標&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 已發現目標，停止監聽&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(SLEEPTIME)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Exception&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 執行期間錯誤：&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;(t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(), e))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;嘗試搜尋看看：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/K2h43ZZ.webp&#34;
  alt=&#34;&#34;width=&#34;1144&#34; height=&#34;428&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;稍微改個關鍵字來測試找到的狀況：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/xfGkMFo.webp&#34;
  alt=&#34;&#34;width=&#34;874&#34; height=&#34;312&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nkvRBnj.webp&#34;
  alt=&#34;&#34;width=&#34;437&#34; height=&#34;187&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;大功告成！&lt;/p&gt;
&lt;p&gt;雖然還有一些可以拿來玩的地方，例如說呼叫時能輸入關鍵字，或是連接 Line 機器人做通知等等，但算是大致完工了。下面附上完整程式碼：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 爬蟲相關套件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; requests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; requests_html &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; HTML
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 寄信相關套件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; smtplib
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; email.mime.text &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; MIMEText
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 計時器相關套件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; datetime &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; dt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ===== 參數 =====&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;URL &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://www.ptt.cc/bbs/BuyTogether/index.html&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 目標看板網址&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;KEYWORD &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;netflix&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 搜尋關鍵字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SLEEPTIME &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 每輪搜尋休眠時間&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ===== 參數 =====&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;flog &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 判斷是否已尋找到目標用的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime &lt;span style=&#34;color:#75715e&#34;&gt;# 顯示時間用的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(url):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;傳入網址，向 PTT 回答已經滿 18 歲，回傳網頁內容&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; requests&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(url, cookies&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;over18&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1&amp;#39;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; response
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_article_entries&lt;/span&gt;(doc):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;傳入網頁內容，利用 requests_html 取出 div.r-ent 的元素內容並回傳&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    html &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; HTML( html &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; doc )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post_entries &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; html&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.r-ent&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; post_entries
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_article_meta&lt;/span&gt;(entry):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;將 r-ent 元素的內容格式化成 dict 再回傳&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    meta &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;: entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.title&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;push&amp;#39;&lt;/span&gt;: entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.nrec&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;date&amp;#39;&lt;/span&gt;: entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.date&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 正常的文章可以取得作者和連結&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;author&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.author&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;find(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;div.title &amp;gt; a&amp;#39;&lt;/span&gt;, first&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;attrs[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;href&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AttributeError&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 被刪除的文章我們就不要了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;author&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[Deleted]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[Deleted]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; meta
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;send_mail_for_me&lt;/span&gt;(meta):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;利用 Gmail 的服務寄發通知信&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    send_gmail_user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;***@gmail.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    send_gmail_password &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;********&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rece_gmail_user &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*****@gmail.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MIMEText(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;您所追蹤的 &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; KEYWORD &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; 已經出現在板上！&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; 文章：&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;https://www.ptt.cc&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;link&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Subject&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;PTT 監聽通知信&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;From&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; send_gmail_user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;To&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rece_gmail_user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; smtplib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SMTP_SSL(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;smtp.gmail.com&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;465&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ehlo()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;login(send_gmail_user, send_gmail_password)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send_message(msg)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;quit()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;print_meta&lt;/span&gt;(meta):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;列印文章資料，看起來整齊一點&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{push:&amp;lt;3s}{date:&amp;lt;5s}{author:&amp;lt;15s}{title:&amp;lt;40s}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(push &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;push&amp;#39;&lt;/span&gt;], date &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;date&amp;#39;&lt;/span&gt;], author &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;author&amp;#39;&lt;/span&gt;], title &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 程式本體&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ptt_alert&lt;/span&gt;(url, keyword):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; url &lt;span style=&#34;color:#75715e&#34;&gt;# 團購版&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch(url) &lt;span style=&#34;color:#75715e&#34;&gt;# 取得網頁內容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post_entries &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parse_article_entries(resp&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text) &lt;span style=&#34;color:#75715e&#34;&gt;# 取得各列標題&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 連線成功，開始搜尋目標「&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;」&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;(t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(), KEYWORD))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; entry &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; post_entries:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        meta &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parse_article_meta(entry)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 如果找到關鍵字，寄信通知我&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; keyword &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lower() &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;截止&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; meta[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print_meta(meta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            send_mail_for_me(meta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 已發現監聽目標！通知信已寄出&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            flog &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 沒找到的時候就正常顯示&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print_meta(meta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 搜尋完畢，並未發現目標。正在休眠 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; 毫秒並等待下一輪搜尋……&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;(t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(), SLEEPTIME))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 主流程部分&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 開始執行監聽&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ptt_alert(URL, KEYWORD) &lt;span style=&#34;color:#75715e&#34;&gt;# 開始執行主流程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;  flog:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 已發現目標，停止監聽&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(SLEEPTIME)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Exception&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;] 執行期間錯誤：&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt;(t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(), e))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Android: MySQL 連線筆記（使用XAMPP）</title>
      <link>https://igouist.github.io/post/2019/12/android-fetch-mysql-using-xampp/</link>
      <pubDate>Mon, 09 Dec 2019 00:09:10 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2019/12/android-fetch-mysql-using-xampp/</guid>
      <description>&lt;p&gt;之前做給學弟妹參考的簡單筆記，順手放上來，以後遇到的時候可以回來參考。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目標：利用 XAMPP 簡單地架設一個伺服器環境，建立資料庫，並且能在 Android 上取得資料庫的資料。&lt;/p&gt;&lt;/blockquote&gt;
&lt;div class=&#34;toc toc-box&#34;&gt;
    &lt;div class=&#34;toc-title&#34;&gt;章節目錄&lt;/div&gt;
    &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#架設伺服器環境&#34;&gt;架設伺服器環境&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#設定伺服器環境&#34;&gt;設定伺服器環境&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#一更改預設開啟文件的方式&#34;&gt;（一）更改預設開啟文件的方式&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#二設定-mysql-的登入驗證方式&#34;&gt;（二）設定 MySQL 的登入驗證方式&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#三建立資料庫與資料表&#34;&gt;（三）建立資料庫與資料表&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#建立連線用檔案&#34;&gt;建立連線用檔案&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#在-android-連線取得資料&#34;&gt;在 Android 連線取得資料&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;h2 id=&#34;架設伺服器環境&#34;&gt;架設伺服器環境&lt;/h2&gt;
&lt;p&gt;在電腦架設伺服器環境的工具有很多種，例如 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10190366&#34;&gt;Windows 內建就有的 IIS&lt;/a&gt;（通常拿來搭配 ASP.net）以及微軟的 &lt;a href=&#34;https://snippetinfo.net/media/1869&#34;&gt;WAMP&lt;/a&gt; 等等，或是直接用 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10075496&#34;&gt;Node.js&lt;/a&gt; 跑環境兼後端，不勝枚舉。今天要拿來實作的是 &lt;strong&gt;XAMPP&lt;/strong&gt; 這一款。XAMPP 嚴格說起來並不算是一個軟體，而是一個&lt;strong&gt;架站懶人包&lt;/strong&gt;，它的名字是由這些東西組成的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;X = 跨平台&lt;/li&gt;
&lt;li&gt;A = Apache：網頁伺服器軟體&lt;/li&gt;
&lt;li&gt;M = MySQL：資料庫軟體&lt;/li&gt;
&lt;li&gt;P = PHP：程式語言，可以閱覽 &lt;a href=&#34;http://www.w3school.com.cn/php/&#34;&gt;w3school：PHP&lt;/a&gt; 和 &lt;a href=&#34;http://www.runoob.com/php/php-tutorial.html&#34;&gt;菜鳥教程：PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;P = Perl：程式語言，可以閱覽 &lt;a href=&#34;http://www.runoob.com/perl/perl-tutorial.html&#34;&gt;菜鳥教程：Perl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外還包含了一些信件軟體、伺服器紀錄軟體等等，遇見的時候會再解釋。&lt;/p&gt;
&lt;p&gt;首先請到 &lt;a href=&#34;https://www.apachefriends.org/zh_tw/index.html&#34;&gt;XAMPP 官方網站&lt;/a&gt;下載最新版本的 XAMPP，下載完畢後開啟安裝。允許安裝之後什麼都還沒做，就會先送你一個警告視窗：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ttMHySF.webp&#34;
  alt=&#34;&#34;width=&#34;506&#34; height=&#34;152&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;簡單來說提醒你就是因為 UAC（使用者帳戶控制）的關係，請不要安裝到 &lt;code&gt;C:\&lt;/code&gt; 啦、&lt;code&gt;C:\Program Files(x86)&lt;/code&gt; 這些 XAMPP 可能會無法取得權限的地方。按下 OK 之後就可以正式進入安裝程序了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PVNIstP.webp&#34;
  alt=&#34;&#34;width=&#34;507&#34; height=&#34;431&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就是勾選要安裝的軟體，預設是全選，稍微說明一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FileZila FTP Server：檔案傳輸用的，要讓使用者上傳下載檔案就需要這個&lt;/li&gt;
&lt;li&gt;Mercury Mail Server：電子郵件系統，會需要由伺服器寄信收信的時候使用&lt;/li&gt;
&lt;li&gt;Tomcat：&lt;del&gt;追老鼠用的&lt;/del&gt; &lt;a href=&#34;http://www.runoob.com/jsp/jsp-tutorial.html&#34;&gt;.jsp 架構&lt;/a&gt;的網頁用的&lt;/li&gt;
&lt;li&gt;phpMyAdmin：MySQL 的圖形化操作介面，必裝，真的Hen方便&lt;/li&gt;
&lt;li&gt;Webalizer：紀錄伺服器LOG和分析程式&lt;/li&gt;
&lt;li&gt;Fake Sendmail：搭配 Mail Server 服用的，虛擬 EMail&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;del&gt;說這麼多但其實我都直接按 Next&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;下一頁會要求選擇安裝路徑，預設是 &lt;code&gt;C:\xampp&lt;/code&gt;，不用更改（如果非得要改位置的話，記得前面有警告過要先確認安裝位置資料夾的權限）&lt;/p&gt;
&lt;p&gt;之後就是一些免費聲明、看看我們官網之類的，一直下一步就會開始安裝了。安裝完畢後會直接開啟 XAMPP 的主控台。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PQLHjN1.webp&#34;
  alt=&#34;&#34;width=&#34;667&#34; height=&#34;430&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;選擇完語言進入主控台後，先試著把 Apache 和 MySQL 開起來試試。等下面的訊息視窗跑完，Apache 和 MySQL 顯示綠色後就代表開啟成功了。（紅色的話就代表要去 Google 了）&lt;/p&gt;
&lt;p&gt;打開瀏覽器，在網址列輸入 &lt;code&gt;Localhost&lt;/code&gt; 或 &lt;code&gt;127.0.0.1&lt;/code&gt;，或是直接在主控台 Apache 的部分按下 Admin 來看本機預設網頁是否連得上，如果能看見下面這個畫面就代表 XAMPP 有開啟了 80 Port 並註冊防火牆，伺服器軟體有正常運作。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZLn2hpc.webp&#34;
  alt=&#34;&#34;width=&#34;1704&#34; height=&#34;853&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著檢查 MySQL 有沒有正常運作，我們有下載 phpMyAdmin，所以直接到 &lt;code&gt;http://localhost/phpmyadmin/&lt;/code&gt; 或在主控台 MySQL 的部分按下 Admin 看能不能看見 MySQL 的主控台。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BEGogla.webp&#34;
  alt=&#34;&#34;width=&#34;1911&#34; height=&#34;752&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;都確定開得起來之後，伺服器環境就架設完畢了，接著要開始進行設定。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：由於 MySQL 和 XAMPP 就是一個綁定的關係，且有簡單安裝使用的特性，因此本篇的資料庫都以 MySQL 做說明。當然在實際應用的時候還有很多不同的資料庫可以選擇，例如&lt;a href=&#34;https://docs.microsoft.com/zh-tw/sql/relational-databases/lesson-1-connecting-to-the-database-engine?view=sql-server-2017&#34;&gt;微軟的 SQL Server&lt;/a&gt;(申請 &lt;a href=&#34;https://imagine.microsoft.com/zh-tw&#34;&gt;microsoft imagine&lt;/a&gt; 可以免費取得)、 Azure 的雲端 SQL Database，甚至是&lt;a href=&#34;https://aws.amazon.com/tw/nosql/&#34;&gt;最近比較潮的 NoSQL&lt;/a&gt;，例如 MongoDB、Cassandra 等等。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;設定伺服器環境&#34;&gt;設定伺服器環境&lt;/h2&gt;
&lt;h3 id=&#34;一更改預設開啟文件的方式&#34;&gt;（一）更改預設開啟文件的方式&lt;/h3&gt;
&lt;p&gt;先宣導一下，在這個步驟你&lt;strong&gt;應該&lt;/strong&gt;先去下載一個編譯器，這邊推薦 &lt;a href=&#34;https://notepad-plus-plus.org&#34;&gt;Notepad++ &lt;/a&gt;。當然如果你已經有慣用的編譯器，像是 &lt;a href=&#34;https://code.visualstudio.com&#34;&gt;VS Code&lt;/a&gt;、&lt;a href=&#34;https://atom.io&#34;&gt;Atom&lt;/a&gt; 甚至 &lt;a href=&#34;https://www.vim.org&#34;&gt;Vim&lt;/a&gt; ，都可以！就是拜託&lt;strong&gt;不要用記事本&lt;/strong&gt;直接開，程式碼會全部擠在一起。當然如果你天生神力就當我沒說過。&lt;/p&gt;
&lt;p&gt;因此第一步就是更改預設的開啟檔案，如下（路徑請選擇你要用的編譯器）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/N8RqnPh.webp&#34;
  alt=&#34;&#34;width=&#34;976&#34; height=&#34;663&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;選好了之後按 Save 就行。&lt;/p&gt;
&lt;h3 id=&#34;二設定-mysql-的登入驗證方式&#34;&gt;（二）設定 MySQL 的登入驗證方式&lt;/h3&gt;
&lt;p&gt;前面測試的時候直接就可以進入資料庫的控制頁面是非常危險的，任何人使用你的電腦都可以自由進出資料庫，過於羞恥，因此我們首先要先更改進入資料庫時的驗證方式。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://image.igouist.net/ykdGwAs.webp&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在 Apache 的 config 裡面找到 phpMyAdmin，會打開設定檔，接著找到以下這段&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/q4zxpaF.webp&#34;
  alt=&#34;&#34;width=&#34;422&#34; height=&#34;134&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;auth_type 是指連線方式。&lt;strong&gt;預設是 config，我們需要把它改成 cookie&lt;/strong&gt;。它們間的差別在於 config 是將帳號及密碼存在 &lt;code&gt;config.inc.php&lt;/code&gt;，之後自動登入；而 cookie：用資料庫驗證帳號密碼登入，比較安全。更改完記得按存檔，存檔之後回到主控台把 Apache 和 MySQL 都關掉重開，重啟後就可以到 MySQL 的控制頁面。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ONA96MP.webp&#34;
  alt=&#34;&#34;width=&#34;516&#34; height=&#34;469&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;再度進入 MySQL 的控制頁面後應該就會有要求登入的視窗出現了。預設的帳號是root，密碼是空白。當然建議登入後先點選修改密碼把預設的空白密碼改掉。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/jJh5I9X.webp&#34;
  alt=&#34;&#34;width=&#34;1907&#34; height=&#34;722&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h3 id=&#34;三建立資料庫與資料表&#34;&gt;（三）建立資料庫與資料表&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;有基礎的 SQL 和資料庫的概念會更好，你可能會需要 &lt;a href=&#34;https://www.1keydata.com/tw/sql/sql.html&#34;&gt;SQL語法教學&lt;/a&gt; 或  &lt;a href=&#34;http://www.sqlzoo.net/&#34;&gt;A Gentle Introduction to SQL&lt;/a&gt; 以及一些關於資料庫用語的知識&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;修改密碼後，我們來練習實際建一個資料庫。點選上方進入資料庫列表&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WyoqSoU.webp&#34;
  alt=&#34;&#34;width=&#34;688&#34; height=&#34;509&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著可以看見預設的系統資料庫，我們直接建立一個新的。第一個欄位是輸入資料庫名稱，請只輸入小寫字母和底線，不要有大寫字母。&lt;/p&gt;
&lt;p&gt;第二個欄位則是資料庫的編碼，我們屬於中文語系，utf8_unicode_ci 和 utf8_general_ci 是最常用的，雖然 utf8_general_ci 對某些語言的支援有一些小問題，但是速度較快。而 utf8_unicode_ci 則比較精確，不過速度會慢一些&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TfCvdU0.webp&#34;
  alt=&#34;&#34;width=&#34;486&#34; height=&#34;199&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;建立資料庫之後，我們還必須先建立一個資料表。這兩者之間的關係就像是 Excel 檔案和其中的一個分頁一樣。輸入名稱（一樣只用小寫字母）和欄位的數量之後就可以按下執行了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/w1Yuypd.webp&#34;
  alt=&#34;&#34;width=&#34;1141&#34; height=&#34;566&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就是資料表詳細設計的部分，最左邊是該欄位的&lt;strong&gt;名稱&lt;/strong&gt;，名稱一樣請不要包含大寫字母，會產生一些錯誤。&lt;/p&gt;
&lt;p&gt;接著是&lt;strong&gt;資料型別&lt;/strong&gt;（關於SQL 的資料型別部分，可以參見&lt;a href=&#34;https://docs.microsoft.com/zh-tw/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-2017&#34;&gt;微軟技術中心&lt;/a&gt;或&lt;a href=&#34;https://www.tad0616.net/modules/tad_book3/html.php?tbdsn=455&#34;&gt;SQL常用資料類型&lt;/a&gt;）較常用的有 int 整數、float 浮點數、varcher 變動長度字串、date 日期與 time 時間等等。接著需要定義長度。通常名稱、型態、長度會是最重要的。&lt;/p&gt;
&lt;p&gt;接著可以注意到右邊有個 A_I，是 &lt;strong&gt;Auto Increment&lt;/strong&gt;，通常用於 ID 、表單編號這類資料上面，是指該欄位是由系統自動填寫，每次新增資料的時候自動給值，如此就能讓第一筆是 1、第二筆是 2 這樣子自動 +1，不用人工輸入。預設會由 1 開始，每次增加 1 ，按下 A_I 之後，會確認前綴字元的數量，像我們使用 INT 的場合可以不用填入直接按確認。另一個常用的是&lt;strong&gt;空值&lt;/strong&gt;，如果勾選就代表該欄位可以接受 Null，反之則不可。&lt;/p&gt;
&lt;p&gt;下部分會要求給資料表&lt;strong&gt;註解&lt;/strong&gt;，以及該&lt;strong&gt;資料表的資料類型&lt;/strong&gt;，沒有填寫的話預設是參照資料庫的編碼，這部分如前所述。最後右下角部分有預覽 SQL 以及儲存的功能。預覽 SQL 可以產生這一頁操作的 SQL 指令。（即使用了圖形化控制台，還是有很多地方可以使用指令進行操作比較方便）&lt;/p&gt;
&lt;p&gt;完成基本資料之後就可以試著執行，能看到資料表的表格就算是成功建立了&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/O8JDdqu.webp&#34;
  alt=&#34;&#34;width=&#34;753&#34; height=&#34;335&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;警告：如果在建立之後跳出了 illegal string offset 錯誤，極有可能是資料庫或資料表等名稱使用了大寫字母或特殊字元，導致 MySQL 建立資料庫的 PHP 檔案發生錯誤。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;現在我們已經建立個一個新的資料庫，也建立了新的資料表，現在讓我們更改該資料庫的權限，增加一個能連線到此資料庫的帳戶。請在左側點選剛剛建立的資料庫，並點擊上方工具列的權限。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/OeI1Xwv.webp&#34;
  alt=&#34;&#34;width=&#34;1074&#34; height=&#34;435&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;




&lt;img
  src=&#34;https://image.igouist.net/X3bLMTK.webp&#34;
  alt=&#34;&#34;width=&#34;597&#34; height=&#34;314&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著可以看到能存取該資料庫的帳戶，現在當然只有預設的 root。現在嘗試建立一個新的使用者帳號。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/nwQqRtY.webp&#34;
  alt=&#34;&#34;width=&#34;907&#34; height=&#34;930&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;輸入帳號和密碼，主機通常是選擇限制本機登入，之後再遠端操作。下部分是權限相關的部分，可以直接全選。確認完之後就可以按下執行，如此就可以新增新的使用者了。&lt;/p&gt;
&lt;p&gt;最後我們得在剛剛新建立的資料表中，利用上面工具列的 新增 或 SQL 頁面替資料表加入幾筆資料，後續比較方便測試。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在這一章你必須學會新建資料庫、資料表、使用者以及設定權限。現在你可以花點時間&lt;strong&gt;自由摸索 phpMyAdmin 的操作介面&lt;/strong&gt;。記住，不要把系統資料庫玩壞。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;建立連線用檔案&#34;&gt;建立連線用檔案&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你需要了解 PHP 相關語法，可以參閱 &lt;a href=&#34;http://www.w3school.com.cn/php/&#34;&gt;w3school：PHP&lt;/a&gt; 和 &lt;a href=&#34;http://www.runoob.com/php/php-tutorial.html&#34;&gt;菜鳥教程：PHP&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;一般來說，我們會在伺服器上預先寫好一個腳本檔案，主要負責去和資料庫連線、執行我們寫好的 SQL 取得資料並回傳；之後我們像是手機、網頁需要資料的時候，只需要去呼叫這個腳本檔案就行了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hiSn00p.webp&#34;
  alt=&#34;&#34;width=&#34;280&#34; height=&#34;122&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣做可以只執行我們預先準備好的 SQL 語法和語法的限制，提高安全性（你不會希望可以由手機或網頁端等等&lt;a href=&#34;https://www.puritys.me/docs-blog/article-11-SQL-Injection-%E5%B8%B8%E8%A6%8B%E7%9A%84%E9%A7%AD%E5%AE%A2%E6%94%BB%E6%93%8A%E6%96%B9%E5%BC%8F.html&#34;&gt;自由地撰寫或更改SQL語法給資料庫&lt;/a&gt;的，當然你膽子夠大&lt;del&gt;或是專題快來不及了&lt;/del&gt;的話再考慮嘗試從手機端下 SQL 指令給吧)，也可以將同樣的工作腳本重複使用，不同的工作加以區別。總之，我們需要在伺服器端的電腦上先編寫一個腳本檔案來取得資料庫的資料。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：本篇以 PHP 為範例，其他可在伺服器運行的語言如 Asp.net C#、Python 等等也都可以做出回傳資料的網頁接口，應考量選用的資料庫加以選擇。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;XAMPP 的網站資料夾預設在 &lt;code&gt;C:\xampp\htdocs&lt;/code&gt;，我們在此新增一個 PHP 檔案命名為 GetData.php 並開啟。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;?&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;php&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 設定 MySQL 的連線資訊並開啟連線
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 資料庫位置、使用者名稱、使用者密碼、資料庫名稱
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    $link &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mysqli_connect&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;******&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;newdatabase&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $link &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;set_charset&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;UTF8&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 設定語系避免亂碼
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// SQL 指令
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    $result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $link &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM `newtable`&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ($row &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $result&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fetch_assoc&lt;/span&gt;()) &lt;span style=&#34;color:#75715e&#34;&gt;// 當該指令執行有回傳
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        $output[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; $row; &lt;span style=&#34;color:#75715e&#34;&gt;// 就逐項將回傳的東西放到陣列中
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 將資料陣列轉成 Json 並顯示在網頁上，並要求不把中文編成 UNICODE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;json_encode&lt;/span&gt;($output, &lt;span style=&#34;color:#a6e22e&#34;&gt;JSON_UNESCAPED_UNICODE&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $link &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 關閉資料庫連線
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;?&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/TDwalqp.webp&#34;
  alt=&#34;&#34;width=&#34;671&#34; height=&#34;327&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;完成連線至資料庫的程式後，請實際開啟瀏覽器輸入 localhost/GetData.php 測試看看能不能抓到資料。（如果是一片空白，請先確認資料表是否已經有新增資料，接著確認是否有成功連線）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：搜尋 MySQL 相關的 PHP 語法時，可能會有 mysql 和 mysqli 兩種不同的方法。然而 mysql 已經在 PHP 7.0 被廢除，因此 GOOGLE 時請不要找太久遠的資料。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VbC4Sgz.webp&#34;
  alt=&#34;&#34;width=&#34;924&#34; height=&#34;292&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;成功的話應該能看見網頁輸出了資料表中資料的 JSON，之後我們就是要用 Android 連線到這個網頁並取得資料。此外，若是要搜尋不同資料表的資料或是特定條件的資料，只需要更改 SQL 語法即可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：關於 JSON 可以參閱 &lt;a href=&#34;http://miniaspreading.github.io/guide-to-json/1-what-is-json.html&#34;&gt;JSON精要讀書紀錄&lt;/a&gt;，另外常用的 SQL 操作還有新增、修改、刪除等，有興趣的同學可以嘗試自己實作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;警告&lt;/strong&gt;：本篇僅教學如何連線取值，實際應用時請對取得資料的網頁進行&lt;strong&gt;資料驗證或加密&lt;/strong&gt;。你可能需要了解 &lt;a href=&#34;https://blog.toright.com/posts/1203/%E6%B7%BA%E8%AB%87-http-method%EF%BC%9A%E8%A1%A8%E5%96%AE%E4%B8%AD%E7%9A%84-get-%E8%88%87-post-%E6%9C%89%E4%BB%80%E9%BA%BC%E5%B7%AE%E5%88%A5%EF%BC%9F.html&#34;&gt;GET 和 POST 的差異&lt;/a&gt; 、於 PHP 端要求傳入特定的參數進行驗證後才能取得資料等等。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;在-android-連線取得資料&#34;&gt;在 Android 連線取得資料&lt;/h2&gt;
&lt;p&gt;打開 Android studio，建立新專案，然後簡單開個版面如下就好，目標是按下按鈕之後把資料顯示在 Textview 裡面。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/M5GJLzV.webp&#34;
  alt=&#34;&#34;width=&#34;1238&#34; height=&#34;849&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著我們開啟 MainActivity.java，先宣告按鈕並寫上監聽事件，這次教直接在程式碼內做事件宣告的寫法，請在 onCreate 的部分撰寫以下程式碼，讓 Android 在一開始執行的時候就連接好按鈕事件。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;protected&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onCreate&lt;/span&gt;(Bundle savedInstanceState) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;super&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;onCreate&lt;/span&gt;(savedInstanceState);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setContentView(R.&lt;span style=&#34;color:#a6e22e&#34;&gt;layout&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;activity_main&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Button button &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; findViewById(R.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;button&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 宣告按鈕&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 宣告按鈕的監聽器監聽按鈕是否被按下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 跟上次在 View 設定的方式並不一樣，是在程式碼做設定&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 我只是覺得好像應該也教一下這種寫法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.&lt;span style=&#34;color:#a6e22e&#34;&gt;setOnClickListener&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; View.&lt;span style=&#34;color:#a6e22e&#34;&gt;OnClickListener&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 按鈕事件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;(View view) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 按下之後會執行的程式碼，可以直接寫也可以呼叫方法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著我們要建立一個函式處理連線的部分，由於我們會用到 Apache 的 HTTP 套件，我們得先引用套件包。請先打開 Build.gradle 在 android 的部分加入 &lt;code&gt;useLibrary &#39;org.apache.http.legacy&#39;&lt;/code&gt; 進行引用。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/t895n3N.webp&#34;
  alt=&#34;&#34;width=&#34;975&#34; height=&#34;611&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;引用完畢之後上面會顯示通知要求你重新同步這個專案，按下 sync now 之後等待它專案同步完畢。接著我們還得要上網的權限才能取得資料，因此要到 AndroidManifest.xml 這地方加上網路權限的許可 &lt;code&gt;&amp;lt;uses-permission android:name=&amp;quot;android.permission.INTERNET&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/pIRps1G.webp&#34;
  alt=&#34;&#34;width=&#34;964&#34; height=&#34;587&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;引用了工具也取得權限之後，我們就可以回到 Java 程式碼的部分了。這邊先按每一段進行解說，&lt;strong&gt;最後會貼上整頁的程式碼&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;整體程式的思路是這樣的：由於 Android 本身的限制，和網路連線的部分必須要用另一個執行緒，不能干涉到主流程，因此我們需要先宣告一個執行緒。而它執行的事件部分，我們需要先宣告和 HTTP 有關的函式庫和物件，接著利用這些物件連線到我們伺服器上的網頁，最後把網頁上的資料拉回來。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：HTTP 的部分，可以參考 &lt;a href=&#34;http://fiend1120.pixnet.net/blog/post/193711428&#34;&gt;Android HTTP Get 及 Post&lt;/a&gt; &lt;br/&gt;而執行緒的部分，可以參考 &lt;a href=&#34;https://medium.com/@totoroLiu/program-process-thread-%E5%B7%AE%E7%95%B0-4a360c7345e5&#34;&gt;Program/Process/Thread 差異&lt;/a&gt; 以及 &lt;a href=&#34;http://ccckmit.wikidot.com/thread&#34;&gt;Thread 的概念&lt;/a&gt;&lt;br/&gt;和 Android 相關的程式部分可以參考 &lt;a href=&#34;http://andcooker.blogspot.com/2012/09/android-runnable-handler.html&#34;&gt;Android 執行緒 - Runnable 與 Handler&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2020.04.21 補充：HTTP 的部分可以看 &lt;a href=&#34;https://medium.com/@hulitw/learning-tc-ip-http-via-sending-letter-5d3299203660&#34;&gt;從傳紙條輕鬆學習基本網路概念&lt;/a&gt; 這篇，寫得很不錯又好懂&lt;/p&gt;
&lt;p&gt;2021.06.02 補充：在之後的 .NET 的系列文補上了 HTTP 和 API 的一些基本知識。有興趣的朋友可以參閱本部落格的這篇 &lt;a href=&#34;https://igouist.github.io/post/2021/05/newbie-2-webapi&#34;&gt;認識 Api 與建置 Web Api 服務&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先是建立一個執行緒的部分：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 建立一個執行緒執行的事件取得網路資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Android 有規定，連線網際網路的動作都不能再主線程做執行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 畢竟如果使用者連上網路結果等太久整個系統流程就卡死了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Runnable mutiThread &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Runnable(){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 當這個執行緒完全跑完後執行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            runOnUiThread(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Runnable() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著在裡面撰寫處理 HTTP 相關的部分：&lt;/p&gt;
&lt;p&gt;（註：先用圖片說明，程式碼將放於最後）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gN7AxTX.webp&#34;
  alt=&#34;&#34;width=&#34;668&#34; height=&#34;137&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;用上面宣告的物件開始進行連線並處理取得的資料：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/LpURZmA.webp&#34;
  alt=&#34;&#34;width=&#34;696&#34; height=&#34;194&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在執行緒完成的時候放到 textview 裡面：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JzLk5he.webp&#34;
  alt=&#34;&#34;width=&#34;309&#34; height=&#34;116&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;最後在程式建立時把執行緒放到按鈕的事件裡面，讓按鈕可以觸發這一套流程&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/qG2NQPN.webp&#34;
  alt=&#34;&#34;width=&#34;479&#34; height=&#34;387&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;完成的整個程式碼如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MainActivity&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;extends&lt;/span&gt; AppCompatActivity {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    TextView textView; &lt;span style=&#34;color:#75715e&#34;&gt;// 把視圖的元件宣告成全域變數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Button button;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    String result; &lt;span style=&#34;color:#75715e&#34;&gt;// 儲存資料用的字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;protected&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onCreate&lt;/span&gt;(Bundle savedInstanceState) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;super&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;onCreate&lt;/span&gt;(savedInstanceState);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        setContentView(R.&lt;span style=&#34;color:#a6e22e&#34;&gt;layout&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;activity_main&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;// 找到視圖的元件並連接&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        button &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; findViewById(R.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;button&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        textView &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; findViewById(R.&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;textView&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 宣告按鈕的監聽器監聽按鈕是否被按下&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 跟上次在 View 設定的方式並不一樣&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// 我只是覺得好像應該也教一下這種寫法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        button.&lt;span style=&#34;color:#a6e22e&#34;&gt;setOnClickListener&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; View.&lt;span style=&#34;color:#a6e22e&#34;&gt;OnClickListener&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 按鈕事件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;(View view) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 按下之後會執行的程式碼&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 宣告執行緒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Thread thread &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Thread(mutiThread); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                thread.&lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 開始執行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;/* ======================================== */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 建立一個執行緒執行的事件取得網路資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Android 有規定，連線網際網路的動作都不能再主線程做執行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 畢竟如果使用者連上網路結果等太久整個系統流程就卡死了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; Runnable mutiThread &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Runnable(){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                URL url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; URL(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://140.127.35.130/GetData.php&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 開始宣告 HTTP 連線需要的物件，這邊通常都是一綑的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HttpURLConnection connection &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (HttpURLConnection) url.&lt;span style=&#34;color:#a6e22e&#34;&gt;openConnection&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 建立 Google 比較挺的 HttpURLConnection 物件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                connection.&lt;span style=&#34;color:#a6e22e&#34;&gt;setRequestMethod&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 設定連線方式為 POST&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                connection.&lt;span style=&#34;color:#a6e22e&#34;&gt;setDoOutput&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 允許輸出&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                connection.&lt;span style=&#34;color:#a6e22e&#34;&gt;setDoInput&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 允許讀入&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                connection.&lt;span style=&#34;color:#a6e22e&#34;&gt;setUseCaches&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 不使用快取&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                connection.&lt;span style=&#34;color:#a6e22e&#34;&gt;connect&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 開始連線&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; responseCode &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    connection.&lt;span style=&#34;color:#a6e22e&#34;&gt;getResponseCode&lt;/span&gt;(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 建立取得回應的物件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(responseCode &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   HttpURLConnection.&lt;span style=&#34;color:#a6e22e&#34;&gt;HTTP_OK&lt;/span&gt;){ 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;// 如果 HTTP 回傳狀態是 OK ，而不是 Error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    InputStream inputStream &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        connection.&lt;span style=&#34;color:#a6e22e&#34;&gt;getInputStream&lt;/span&gt;(); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;// 取得輸入串流&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    BufferedReader bufReader &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; BufferedReader(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; InputStreamReader(inputStream, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;), 8); 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;// 讀取輸入串流的資料&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    String box &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 宣告存放用字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    String line &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 宣告讀取用的字串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt;((line &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; bufReader.&lt;span style=&#34;color:#a6e22e&#34;&gt;readLine&lt;/span&gt;()) &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        box &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; line &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#75715e&#34;&gt;// 每當讀取出一列，就加到存放字串後面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    inputStream.&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 關閉輸入串流&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; box; &lt;span style=&#34;color:#75715e&#34;&gt;// 把存放用字串放到全域變數&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 讀取輸入串流並存到字串的部分&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 取得資料後想用不同的格式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 例如 Json 等等，都是在這一段做處理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt;(Exception e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; e.&lt;span style=&#34;color:#a6e22e&#34;&gt;toString&lt;/span&gt;(); &lt;span style=&#34;color:#75715e&#34;&gt;// 如果出事，回傳錯誤訊息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 當這個執行緒完全跑完後執行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            runOnUiThread(&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Runnable() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    textView.&lt;span style=&#34;color:#a6e22e&#34;&gt;setText&lt;/span&gt;(result); &lt;span style=&#34;color:#75715e&#34;&gt;// 更改顯示文字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而執行結果會像這樣：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/YpuFWMd.webp&#34;
  alt=&#34;&#34;width=&#34;578&#34; height=&#34;853&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在你可以使用 Android 取得資料庫的資料了，快嘗試應用這個技術做一個 APP 來玩吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本篇完成後，有興趣或希望&lt;strong&gt;進階&lt;/strong&gt;學習的同學可以嘗試挑戰以下的部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;連接 MySQL 的 PHP 部分，製作新增、修改、刪除&lt;/li&gt;
&lt;li&gt;另外利用 PHP 結合 HTML，製作出能新增刪除資料表內容的網頁&lt;/li&gt;
&lt;li&gt;Android 向 PHP 取得資料時，在 PHP 端實做資料驗證，令 Android 端必須傳遞一個符合的 token 才能取得資料&lt;/li&gt;
&lt;li&gt;Android 向 PHP 取得資料時，使資料在 PHP 端進行加密，而在 Android 端進行解密。&lt;/li&gt;
&lt;li&gt;Android 取得資料後，嘗試使用 json 儲存取得的資料&lt;/li&gt;
&lt;li&gt;Android 取得資料後，使用 ListView 按照格式顯示每一筆資料（可以依序參考&lt;a href=&#34;http://learnexp.tw/%e3%80%90android%e3%80%91listview-%e6%95%99%e5%ad%b8-%e5%be%9e-4%e5%88%b05/&#34;&gt;這篇&lt;/a&gt;、&lt;a href=&#34;http://huli.logdown.com/posts/280137-android-custom-listview&#34;&gt;這篇&lt;/a&gt;和&lt;a href=&#34;https://codemango.blogspot.com/2017/04/android-studio-listview-filter.html&#34;&gt;這篇&lt;/a&gt;）&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;</description>
    </item>
    
    <item>
      <title>Asp.net MVC: 連線資料庫、簡單實作 CRUD</title>
      <link>https://igouist.github.io/post/2019/12/aspnet-connect-db/</link>
      <pubDate>Mon, 09 Dec 2019 00:09:09 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2019/12/aspnet-connect-db/</guid>
      <description>&lt;p&gt;在教學時直接使用 EF 對資料庫跑繫結的方式產生各頁面，但得到了「點一點東西就跑出來了搞不懂呀」的回饋，心想有道理。因此從頭開始實作一遍，並記錄下來。&lt;s&gt;（雖然做完還是覺得，直接用 EF 跑的話果然比較安全方便啊）&lt;/s&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目標：&lt;strong&gt;實作一個 MVC 架構，具資料庫基本操作功能的網站&lt;/strong&gt;，其中包含連線至資料庫的 model、對其進行調用的 controller 以及顯示的 view。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;註：本文預設已在本地電腦上安裝了 &lt;a href=&#34;https://www.microsoft.com/zh-tw/sql-server/sql-server-editions-express&#34;&gt;SQL Server&lt;/a&gt;，並且建立了測試用的資料庫 Test 及表 card，詳情會在文章內述。另外，由於在寫這邊的時候是為了練習手動從編碼開始嘗試連線，故將不使用 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application&#34;&gt;EF 連線產生 Edmx&lt;/a&gt; 的方式，而是直接手工編寫程式碼進行操作。&lt;/p&gt;
&lt;p&gt;另外，關於直接從資料表自動產生可操作的頁面，亦即使用 Entity Framework 做資料繫結的方式，請見 &lt;a href=&#34;https://igouist.github.io/post/2019/12/aspnet-connect-db-ef/&#34;&gt;Asp.net MVC 筆記：Entity Framework 連線資料庫&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;稍微補充一些簡單說明，給那些對 MVC 概念不是很熟悉的朋友：&lt;/p&gt;
&lt;p&gt;MVC 是一種模式，就是做出一個程式的規則和架構，讓大家都按著這個架構撰寫程式碼。如此一來就可以簡化程式的開發過程，也增加了程式的可維護性和可讀性，適合多人同時作業——畢竟什麼該放在哪裡大家都有個共識。&lt;/p&gt;
&lt;p&gt;而 MVC 顧名思義，就是將程式碼分成三個區塊：M、V 和 C。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt;：演算法、物件、資料處理等。像是數學邏輯、連接資料庫取得資料、狗的物件和拉不拉多的物件等等都放這裡&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View&lt;/strong&gt;：使用者會看到的部份，網頁的外觀。Html、Css 就是在這區工作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controller&lt;/strong&gt;：流程控制和資料傳輸。也就是取得使用者傳送來的資料，決定讓哪支程式和哪個頁面出來做事，以及把 Model 送來的資料做處理後丟往 View 等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在小組報告的時候，Model 通常是默默做事的那個，View 則是專門上台報告的，Controller 負責指揮大家做事和組員間的溝通以及講幹話。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;首先我們從建立專案開始操作。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/eRyGBTb.webp&#34;
  alt=&#34;&#34;width=&#34;625&#34; height=&#34;323&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;選擇 .NET 的框架，並且在下面替專案取個名字。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fdIYiCe.webp&#34;
  alt=&#34;&#34;width=&#34;941&#34; height=&#34;657&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;確認選擇的框架是 MVC。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/otUKDR0.webp&#34;
  alt=&#34;&#34;width=&#34;787&#34; height=&#34;517&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;建立連線資料庫用的-model&#34;&gt;建立連線資料庫用的 Model&lt;/h2&gt;
&lt;p&gt;首先先在方案總管的 Models 資料夾裡新增一個類別。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3L0gFuu.webp&#34;
  alt=&#34;&#34;width=&#34;860&#34; height=&#34;516&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;由於我們是從平地起家，手工連線，因此我們這邊選擇空白類別就可以了，取個淺顯易懂的名字並按下新增。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/JCnwaMU.webp&#34;
  alt=&#34;&#34;width=&#34;941&#34; height=&#34;657&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;新增之後應該能看見我們的類別。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/hQn75wu.webp&#34;
  alt=&#34;&#34;width=&#34;894&#34; height=&#34;396&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在連線到資料庫的時候，首先要注意的就是&lt;strong&gt;連線字串&lt;/strong&gt;：連線字串就像是地址加上鑰匙，可以想成你請人幫你到倉庫拿東西，必須先告訴他倉庫在哪再給他鑰匙進去拿一樣，程式必須藉由連線字串才能得知&lt;strong&gt;資料庫的位置和連線資料&lt;/strong&gt;，而不同的資料庫軟體的連線字串格式也有可能不同，例如說 MySQL 的連線字串格式就可以參考&lt;a href=&#34;https://www.itread01.com/p/1192499.html&#34;&gt;這篇&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;而在連線至 MSSQL 的部分，我個人習慣用 Visual studio 內建的功能做產生再按照需求作修改。首先讓我們打開畫面左上方的伺服器總管，並在資料連接上加入一個新的連接。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/VC0CfPE.webp&#34;
  alt=&#34;&#34;width=&#34;621&#34; height=&#34;397&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/G33iM6J.webp&#34;
  alt=&#34;&#34;width=&#34;551&#34; height=&#34;640&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就可以看到加入連接的頁面，我們從上往下進行解說：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;資料來源&lt;/strong&gt;：用來更改資料庫來源的類型，例如 Access 或是其他類型資料庫的時候就可以更改這一項。我們這邊使用 SQL Server 進行示範。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;伺服器名稱&lt;/strong&gt;：SQL Server 的名稱，通常會在 SQL Server 安裝的時候做設定，預設是和電腦一樣。當遠端連線的時候，這邊可以改成輸入 IP。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;驗證&lt;/strong&gt;：比較常用的是 Windows 驗證和 SQL Server 驗證。&lt;strong&gt;Windows 驗證&lt;/strong&gt;即是當資料庫位於本機時，直接使用 Windows 登入者的資料驗證進資料庫。&lt;strong&gt;SQL Server 驗證&lt;/strong&gt;則是使用 SQL Server 中設定的帳號密碼來登入，遠端連線至資料庫取資料的時候就會用帳密連線。&lt;/p&gt;
&lt;p&gt;至於這兩項的選擇需要看使用時的需求以及資料庫本身的設定進行選擇。本篇是直接在 SQL Server 所在的本機上進行架站，同時也沒有特別設定權限，因此直接使用 Windoes 驗證登入就可以了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;資料庫名稱&lt;/strong&gt;：從 Server 裡面選擇要使用的 Database。在此處選擇了示範用的 Test 資料庫。&lt;/p&gt;
&lt;p&gt;以上部分設定完之後，點選左下角的測試連接。若是測試連接沒有問題後，在進階的部分就可以看見已經產生好連線字串了。同時在這個頁面按下確定的話，將會在伺服器總管中連線至資料庫，也能從資料連接的部分對資料庫進行操作。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/eNiiXtj.webp&#34;
  alt=&#34;&#34;width=&#34;554&#34; height=&#34;646&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;↑ 可以從進階的部分看見連線字串。可以稍微看得出來連線字串的格式，其中 Data Source 放伺服器主機、Initial Catalog 放資料庫名稱、Integrated Security 放集成驗證（即 Windows 驗證），此外還有可能會有 UserID 放帳號、Password 放密碼等等。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZssVFCq.webp&#34;
  alt=&#34;&#34;width=&#34;301&#34; height=&#34;355&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;↑ 如果選擇確定的話，就會建立連接。可以直接在這邊右鍵選擇各種功能以操作資料庫。&lt;/p&gt;
&lt;p&gt;現在我們已經有了連線字串，先在我們的類別中將其宣告為一個私有且唯獨的字串常數。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bVnef6n.webp&#34;
  alt=&#34;&#34;width=&#34;769&#34; height=&#34;220&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：在實務上的部份，連線字串通常會放置在網頁設定檔 Web.config 裡，因為網頁設定檔在使用者端是看不見的，所以這樣的做法比較安全，再利用 ConfigurationManager 的函式取得連線字串以使用。關於詳細的操作方式請參考&lt;a href=&#34;https://dotblogs.com.tw/sky5012357/2013/03/19/98161&#34;&gt;這篇&lt;/a&gt;或搜尋「連線字串 Web.config」。本篇為了教學及操作上的方便，才直接放置於程式碼內。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;解決連線字串的問題，接著我們來做一個 model 放我們的資料。可以從上面伺服器總管的圖看見我的資料表 card 有 id、 char_name、 card_name、card_level 四個欄位。&lt;/p&gt;
&lt;p&gt;因此我們新建一個 card 類別如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;namespace&lt;/span&gt; DBconnTest.Models
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ID { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Char_name  { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Card_name  { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; Card_level { &lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;新建完成之後就回到我們的 資料庫連線類別 開始工作。&lt;/p&gt;
&lt;h2 id=&#34;取得所有資料&#34;&gt;取得所有資料&lt;/h2&gt;
&lt;p&gt;第一個目標是能夠取得 Card 資料表的所有資料，因此我們先將方法的部分建立出來，也就是一個會回傳 Card 的 List 的方法。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/utEyL3C.webp&#34;
  alt=&#34;&#34;width=&#34;482&#34; height=&#34;247&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們將會使用 SqlConnection 這個資料庫連線用的工具來操作資料庫，因此需要先在上面 &lt;code&gt;using System.Data.SqlClient&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接著我們想要 GetCards 方法做的事有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用連線字串連線至資料庫&lt;/li&gt;
&lt;li&gt;下 SQL 指令拿到資料&lt;/li&gt;
&lt;li&gt;將拿到的資料做成 Card 物件&lt;/li&gt;
&lt;li&gt;將 Card 們組成一個 List&lt;/li&gt;
&lt;li&gt;回傳 List 給前端使用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;程式碼如下，其後逐步說明。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-CSharp&#34; data-lang=&#34;CSharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; List&amp;lt;Card&amp;gt; GetCards() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    List&amp;lt;Card&amp;gt; cards = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Card&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SqlConnection sqlConnection = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlConnection(ConnStr);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SqlCommand sqlCommand = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; SqlCommand(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SELECT * FROM card&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sqlCommand.Connection = sqlConnection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sqlConnection.Open();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SqlDataReader reader = sqlCommand.ExecuteReader();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (reader.HasRows) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (reader.Read()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Card card = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Card {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ID = reader.GetInt32(reader.GetOrdinal(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Char_name  = reader.GetString(reader.GetOrdinal(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;char_name&amp;#34;&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Card_name  = reader.GetString(reader.GetOrdinal(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;card_name&amp;#34;&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Card_level = reader.GetString(reader.GetOrdinal(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;card_level&amp;#34;&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            cards.Add(card);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Console.WriteLine(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;資料庫為空！&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sqlConnection.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; cards;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KoB6QZU.webp&#34;
  alt=&#34;&#34;width=&#34;917&#34; height=&#34;619&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;首先，我們宣告了一個 Card 的 List 叫做 Cards 用來保存我們等等取得的資料。&lt;/p&gt;
&lt;p&gt;接著我們利用連線字串 ConnStr 來建立了一個 &lt;strong&gt;SQL 連線物件 SqlConnection&lt;/strong&gt;。並宣告了一個 &lt;strong&gt;SQL 命令物件 SqlCommand&lt;/strong&gt; 來放置我們要執行的 SQL 指令。&lt;/p&gt;
&lt;p&gt;這邊使用的是「由 card 資料表選取全部資料(*)」的指令。不論使用的是何種語言，後端連線到資料庫最重要的就是傳送指令至資料庫執行。關於 SQL 指令的使用方式，可以參照 &lt;a href=&#34;https://www.1keydata.com/tw/sql/sql.html&#34;&gt;1keyData 的 SQL 語法&lt;/a&gt; 或是 &lt;a href=&#34;http://www.w3school.com.cn/sql/index.asp&#34;&gt;w3school&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;此外要注意，實務上如果真的需要自己寫連線，下 SQL 的命令和參數時應該用 SqlParameter 的方式讓系統檢查後放入，不應該偷懶直接將字串和參數用 + 接起來就傳送，否則很容易遭遇 SQL injection 的攻擊。&lt;/p&gt;
&lt;p&gt;我們將指令的目標連線指向我們的連線 sqlConnection，完成後&lt;strong&gt;開啟（open）連線&lt;/strong&gt;連至資料庫。&lt;/p&gt;
&lt;p&gt;現在已經和資料庫連線了，但還沒執行我們的指令，宣告一個 &lt;strong&gt;sql 資料讀取物件 SqlDataReader&lt;/strong&gt; 來抓取執行指令（sqlCommand.ExecuteReader）時回傳的資料。&lt;/p&gt;
&lt;p&gt;要注意，當 &lt;strong&gt;SqlDataReader 在讀取資料庫時，是一行一行地讀取&lt;/strong&gt;。因此，我們先判斷是否有讀到資料（HasRows = 有資料行嗎？），若有則逐行進行處理。在 Read() 的時候，運作的方式和我們人類 Read（讀） 書本是一樣的，會讀取一行的資料，因此我們利用 &lt;strong&gt;while&lt;/strong&gt; 迴圈一直閱讀直到不能讀為止。&lt;/p&gt;
&lt;p&gt;當 reader 在讀取資料行時，我們先宣告一個 Card 來放置我們要的資料，這邊為了方便直接用 new 物件 { 資料 } 的方式宣告，跟單純宣告的和定義好的建構式看起來有些不同，但結果會建出一個 card 是一樣的，端看需求選用。&lt;/p&gt;
&lt;p&gt;首先是取得資料的部份，利用 reader.GetOrdinal 去取得該欄位的索引值＼位置。例如說 card_name 是位於資料列的第三欄，那我們就會取得它的索引值為 2 （從 0 開始）。再利用 Get(類型，這邊的類型要配合資料庫內欄位的設定，例如 int32 配 int，string 配 nvarchar 之類) 的指令去取出對應索引值的資料。&lt;/p&gt;
&lt;p&gt;雖然本篇示範的資料庫較小，能夠一目了然欄位所在的位置，下 reader.GetString(2) 也是能夠取得資料的。但其缺點也很明顯，若是資料庫欄位有所變更，抑或是資料庫欄位數量很多的時候，錯誤的風險就會相當大，因此用&lt;strong&gt;欄位名稱尋找索引值，再利用索引值取得資料內容的方式相對比較保險&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;另外，在處理回傳的資料時，會根據習慣和需求而有不同的做法，大多數的作法並不會一樣。例如說也有將回傳資料封裝成 Json 就丟給前端讓前端自己拆包自己頭痛的狀況存在，這部份在看其他人的連線教學或相關文章後就會比較了解。若是需要查詢相關的命令，例如說只執行命令時不回傳值，或單純只傳一個值，可以查詢微軟文件的 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.data.sqlclient.sqlcommand?view=netframework-4.7.2#%E5%82%99%E8%A8%BB&#34;&gt;SqlCommand 頁面&lt;/a&gt; 和 &lt;a href=&#34;https://docs.microsoft.com/zh-tw/dotnet/api/system.data.sqlclient.sqlconnection?view=netframework-4.7.2&#34;&gt;SqlConnection 頁面&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;將資料做成一個 card 後，我們將其放入我們的 List 也就是 cards。當全部的資料都讀完之後，絕對不能忘記關閉連線。最後回傳我們的 cards，到這邊 GetCards 就告一段落了。&lt;/p&gt;
&lt;p&gt;為了測試，我們先到 HomeControlls 的 Index 做一點小修改。using 我們的 model 並且試試看用 GetCards 來取得資料。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/BzylqF3.webp&#34;
  alt=&#34;&#34;width=&#34;721&#34; height=&#34;434&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著把 Home/Index.cshtml 也進行修改。將原本的全部砍掉，為了示範方便，直接 using models 並且用 Viewbag 拿出來看。（註：這邊熟練之後若想了解實務上較建議使用的傳值方式，可以搜尋 &amp;ldquo;ViewModel&amp;rdquo;）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/k7ZIPF6.webp&#34;
  alt=&#34;&#34;width=&#34;634&#34; height=&#34;455&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見資料有正常地取得了&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gGzrAxl.webp&#34;
  alt=&#34;&#34;width=&#34;577&#34; height=&#34;346&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就可以開始處理前端顯示的部份，例如使用 &lt;a href=&#34;https://bootstrap.hexschool.com/docs/4.0/content/tables/&#34;&gt;Bootstrap 的表格&lt;/a&gt; 進行排版，或是其他 css 啦模板啦，都是沒問題的囉！&lt;/p&gt;
&lt;h2 id=&#34;新增資料&#34;&gt;新增資料&lt;/h2&gt;
&lt;p&gt;既然能夠取得資料了，接下來就來做一些基本的操作吧。首先從新增資料開始。&lt;/p&gt;
&lt;p&gt;為了後續操作方便和美觀，先從我們顯示資料的 Index 開始做處理。先將它改成bootstarp 的表格樣式，並且留一格之後放操作項。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;table&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;table&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;thead&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;資料庫編號&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;角色名稱&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;卡片名稱&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;卡片等級&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;thead&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;tbody&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        @foreach (Card card in cards) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;@card.ID&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;@card.Char_name&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;@card.Card_name&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;@card.Card_level&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;tbody&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;table&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6z2UNud.webp&#34;
  alt=&#34;&#34;width=&#34;688&#34; height=&#34;631&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;比剛剛好多了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/EO3xE9b.webp&#34;
  alt=&#34;&#34;width=&#34;916&#34; height=&#34;278&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;既然要新增資料，那一定要有個頁面能夠輸入新增的資料內容。因此我們回到 HomeController 來準備新增一個頁面。首先先把預設的 About 和 Contact 刪掉，也將 Views 裡的這兩頁給刪掉，我們不會用到它們了。&lt;/p&gt;
&lt;p&gt;接著就來建立 CreateCard ：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/T7lxFzj.webp&#34;
  alt=&#34;&#34;width=&#34;590&#34; height=&#34;325&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;之後也必須建立 View 才可以。在 CreatrCard 上點右鍵，選擇新增檢視。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/fS17EpU.webp&#34;
  alt=&#34;&#34;width=&#34;597&#34; height=&#34;580&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Nl7yqjA.webp&#34;
  alt=&#34;&#34;width=&#34;586&#34; height=&#34;326&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;在這一步直接按下 Add 就會新增一個空白的 View 可以使用了。如果想讓系統自己幫你產生，Template 指得就是樣板，裡面已經包含新增刪除等操作會使用到的介面，選擇後再於 Model class 選取要操作的 Model 物件的話，就會自動產生好一個功能頁面讓你修改。不過開頭已經說過這次要純手工，因此我們就直接按下 Add 吧。&lt;/p&gt;
&lt;p&gt;要把資料從 View 丟到 Controller 的方法很多，例如 form 表單或是 ajax 傳值都是可行的。我們這邊用 form 表單做回傳，另外使用 bootstarp 簡單排個版，程式碼如下&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/IGumNlE.webp&#34;
  alt=&#34;&#34;width=&#34;801&#34; height=&#34;441&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;h2&lt;/span&gt;&amp;gt;新增 Card&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;h2&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;br&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;post&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;action&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/Home/CreateCard&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form-group row&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inputCharName&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;col-sm-2&amp;#34;&lt;/span&gt;&amp;gt;角色名稱&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;label&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;col-sm-10&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inputCharName&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Char_name&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form-control&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form-group row&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inputCardName&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;col-sm-2&amp;#34;&lt;/span&gt;&amp;gt;卡片名稱&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;label&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;col-sm-10&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inputCardName&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Card_name&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form-control&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form-group row&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inputCardLevel&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;col-sm-2&amp;#34;&lt;/span&gt;&amp;gt;卡片等級&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;label&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;col-sm-10&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;number&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inputCardLevel&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Card_level&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;form-control&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;btn btn-default&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;submit&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;新增&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;form&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看見我們首先做了一個 form 表單，並&lt;strong&gt;指定這張表單將用 post 的方式將資料傳送到 /Home/CreateCard 這個路徑&lt;/strong&gt;。屆時我們送出的時候，就會送到 HomeController 中有規定收取 post 的 CreateCard() 方法做處理。&lt;/p&gt;
&lt;p&gt;在&lt;strong&gt;表單中最重要的就是各個 input 的 name 屬性&lt;/strong&gt;，當資料傳送的時候，會使用 name 做為資料的欄位名稱。因此這邊的 name 一定要能和我們的 card 物件的資料對應上，才能讓它自動轉換。&lt;/p&gt;
&lt;p&gt;接著用 &lt;a href=&#34;https://v3.bootcss.com/css/#forms&#34;&gt;bootstrap 的格式&lt;/a&gt;做了三組輸入框，以及一個提交按鈕。運行後頁面正常就可以進行下一步了。&lt;/p&gt;
&lt;p&gt;首先我們再度回到 HomeController，做一個 POST 才能進來的 CreateCard 方法來接取表單，並且讓它將傳入的表單資料做成我們建立的 Card 物件。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/yi7ZMvq.webp&#34;
  alt=&#34;&#34;width=&#34;398&#34; height=&#34;176&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這個方法中應該要能將 card 的資料傳入資料庫，但我們還沒有實做傳入資料庫的方法，因此我們要回到當初建立的&lt;strong&gt;資料庫連線用的 model&lt;/strong&gt;（本範例中為 DBconn）去實作一個新增用的函式。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mrVWpCe.webp&#34;
  alt=&#34;&#34;width=&#34;707&#34; height=&#34;248&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;新增的函式跟前面做過的查詢相差無幾，最大的差別在於新增的 SQL 語法是 &lt;a href=&#34;https://www.1keydata.com/tw/sql/sqlinsert.html&#34;&gt;INSERT INTO&lt;/a&gt; 。必須再提醒一次，最注意的一點是，&lt;u&gt;&lt;strong&gt;在@字串中放置的參數，需要用 SqlParameter 的方式讓系統檢查後放入，不應該偷懶直接將字串和參數用 + 接起來就傳送，否則很容易遭遇 &lt;a href=&#34;https://ithelp.ithome.com.tw/articles/10189201&#34;&gt;SQL injection&lt;/a&gt; 的攻擊&lt;/strong&gt;&lt;/u&gt;，被亂下 SQL 指令。&lt;/p&gt;
&lt;p&gt;處理好 model 中控制資料庫的部分之後，我們再回到 Controller 把這段添加上去。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/adCSxgq.webp&#34;
  alt=&#34;&#34;width=&#34;420&#34; height=&#34;285&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到當我們收到表單後，就會嘗試呼叫資料庫物件調用新增的函式做新增，如果新增時出了錯就會把錯誤訊息列印在輸出欄，最後回到首頁。&lt;/p&gt;
&lt;p&gt;最後只要在首頁把新增的按鈕放上去就大功告成囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/mJBPdDA.webp&#34;
  alt=&#34;&#34;width=&#34;725&#34; height=&#34;578&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;試跑幾次流程，可以看到有成功新增資料囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/3ylj9P6.webp&#34;
  alt=&#34;&#34;width=&#34;914&#34; height=&#34;261&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;修改資料&#34;&gt;修改資料&lt;/h2&gt;
&lt;p&gt;接著要嘗試修改資料，修改資料和新增最大的不同是會多一步先將原本的該筆資料取出。首先先將首頁修改一下，在每一行的結尾放上修改資料的連結。由於做成連結的 ActionLink 並不能放進像 model 這麼複雜的資料，所以在實務上都是以傳遞 id 為主。（ ActionLink 會把我們的資料做成連結、用 get 變成網址。所以 editCard.html?ID=2 這個它還看得懂，editCard.html?card=&amp;amp;$%* 這種它就不知道怎麼表達了）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/MVj0OKY.webp&#34;
  alt=&#34;&#34;width=&#34;789&#34; height=&#34;282&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;我們一樣回到 Controller 新增一個方法，只是這次我們要求先把要修改的資料也傳到頁面做處理。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/PLW4abi.webp&#34;
  alt=&#34;&#34;width=&#34;412&#34; height=&#34;81&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我們遇到的問題是：我們需要把 id 變成實際的卡片資料，必須利用這個 id 去取得資料。所以我們到 model 新增一個用 id 搜尋卡片資料的方法。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/r80dGT6.webp&#34;
  alt=&#34;&#34;width=&#34;799&#34; height=&#34;517&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看見單筆查詢的做法基本上和前面做過的拿全部資料所差無幾，只是一個搜全部一個搜條件而已。最大的差異在於我們傳遞了一個 ID 進去，並且在 SQL 指令的地方利用 &lt;a href=&#34;https://www.1keydata.com/tw/sql/sqlwhere.html&#34;&gt;WHERE 下了搜尋條件&lt;/a&gt;，並且只回傳一項資料。&lt;/p&gt;
&lt;p&gt;現在我們回到 Controller 將我們的 ID 搜尋方法放進去，並將回傳的 card 用傳遞 model 的方式傳到 View，期待它能聽話。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/gtNloFh.webp&#34;
  alt=&#34;&#34;width=&#34;425&#34; height=&#34;100&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著我們要新增編輯的頁面（View）了，一樣右鍵新增檢視，並複製新增頁面的程式碼修改如下。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/k9ZyG7o.webp&#34;
  alt=&#34;&#34;width=&#34;1153&#34; height=&#34;661&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;特別標示紅色的部分要多加注意。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先我們傳入了 model，因此要先用 @model 告訴它我們傳遞的是哪個 model&lt;/li&gt;
&lt;li&gt;修改我們表單的回傳目標&lt;/li&gt;
&lt;li&gt;新增一格用來放置 ID，但是 ID 是不能修改的，因此把它設成唯讀&lt;/li&gt;
&lt;li&gt;接著我們將 model 的值先放入各個欄位，才能在修改的時候顯示原本的資料。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;做完這一步的時候可以先從首頁執行是不是可以順利取得單筆資料。&lt;/p&gt;
&lt;p&gt;修改完的資料必須回傳然後告訴資料庫修改的結果，因此我們和新增一樣要做一個 Post 的編輯方法來接收上面的表單。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/8flx7rK.webp&#34;
  alt=&#34;&#34;width=&#34;466&#34; height=&#34;250&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;現在我們該再度前往 資料庫控制的 model 加上修改資料庫內容的方法了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/dkBbur8.webp&#34;
  alt=&#34;&#34;width=&#34;1120&#34; height=&#34;275&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;一樣是和前面的新增資料很像，但這邊使用的 SQL 語法是更新資料用的 &lt;a href=&#34;https://www.1keydata.com/tw/sql/sqlupdate.html&#34;&gt;UPDATE 語法&lt;/a&gt;並且結合了前面的 WHERE 來指定要更改的項目。完成之後我們就可以把它加到 Controller 的方法上囉：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZdyuZub.webp&#34;
  alt=&#34;&#34;width=&#34;433&#34; height=&#34;262&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;這樣就宣告完工了！實際上來編輯一次吧&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/Qtvhalt.webp&#34;
  alt=&#34;&#34;width=&#34;830&#34; height=&#34;237&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;將寫程式修改成打呼看看&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/E9dGERa.webp&#34;
  alt=&#34;&#34;width=&#34;501&#34; height=&#34;292&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;可以看到成功變更囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/KAq6XkV.webp&#34;
  alt=&#34;&#34;width=&#34;1015&#34; height=&#34;208&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;h2 id=&#34;刪除資料&#34;&gt;刪除資料&lt;/h2&gt;
&lt;p&gt;如果前面都大致上了解的話，刪除就是小菜一碟。&lt;/p&gt;
&lt;p&gt;首先我們先回到首頁列表，在編輯後面加上刪除的連結（我是用｜隔開而已，也可以另外新增一格甚至做成按鈕沒關係）&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WTn9MXw.webp&#34;
  alt=&#34;&#34;width=&#34;718&#34; height=&#34;312&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著我們到 Controller 增加一個刪除的方法，因為我們並沒有要做一個「確認刪除嗎？」的頁面，所以最後直接回到首頁列表就可以了。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/ZScM56X.webp&#34;
  alt=&#34;&#34;width=&#34;402&#34; height=&#34;99&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著就到資料庫互動的物件來實作刪除的部分。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/cJe4pic.webp&#34;
  alt=&#34;&#34;width=&#34;758&#34; height=&#34;201&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;非常簡單，只有 SQL 語法的部分有更動，使用的是&lt;a href=&#34;https://www.1keydata.com/tw/sql/sqldelete.html&#34;&gt; DELETE 指令&lt;/a&gt;，由於只是刪除，因此我們果斷地打開連線，砍下去，關閉連線走人。&lt;/p&gt;
&lt;p&gt;回到 Controller 把這個函式添加上去&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/xFyaY8s.webp&#34;
  alt=&#34;&#34;width=&#34;432&#34; height=&#34;119&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;然後就可以試試看能不能刪除資料囉！&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GzrN6Ca.webp&#34;
  alt=&#34;&#34;width=&#34;1034&#34; height=&#34;259&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;點下刪除了之後－－&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/6O9vyNA.webp&#34;
  alt=&#34;&#34;width=&#34;1014&#34; height=&#34;212&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;資料就不見了！（其實蠻危險的？）&lt;/p&gt;
&lt;h2 id=&#34;結語&#34;&gt;結語&lt;/h2&gt;
&lt;p&gt;已經實作了顯示列表、新增、修改、查詢和刪除。所謂萬變不離其宗，基本的資料庫操作都離不開這些方法，熟悉了這些做法的概念之後就沒有問題了，剩下大多是顯示的時候做美化、排序等等的剩餘工作。Good Job！&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Asp.net MVC: Entity Framework 連線資料庫</title>
      <link>https://igouist.github.io/post/2019/12/aspnet-connect-db-ef/</link>
      <pubDate>Mon, 09 Dec 2019 00:09:08 +0800</pubDate>
      <author>Igouist (Igouist)</author>
      <guid>https://igouist.github.io/post/2019/12/aspnet-connect-db-ef/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;目標：將 &lt;a href=&#34;https://igouist.github.io/post/2019/12/aspnet-connect-db/&#34;&gt;上一篇的 Asp.net MVC：連線資料庫、簡單實作 CRUD&lt;/a&gt; 的資料庫基礎功能改成以 &lt;strong&gt;Entity Framework&lt;/strong&gt; 產生的方式跑一遍&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;既然上一篇介紹了土法煉鋼動手做的資料庫取值方式，就覺得不順手把從 EF 建立出基本功能介面的方式記錄下來似乎是有點奇怪。實際上開始寫成筆記之後才發現對內容尚不是很了解，例如一直用 Data First 產生頁面但對 Code First 以程式碼產生架構的方式很不熟。儘管如此仍稍微紀錄一下，之後有更深的了解（例如 ASP.NET Identity 或 Code First 等部分）再進一步做成筆記。另外也可參考較詳細的&lt;a href=&#34;https://docs.microsoft.com/zh-tw/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application&#34;&gt;官方文檔&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;開一個新的專案來示範。前面新增專案的部分都和前一篇一樣，檔案 → 新增專案 → .NET Framework → MVC 這樣&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;備註：旁邊有個變更驗證的部分是 ASP.NET Identity，在做會員系統的時候有用過，但這篇不會用到，為怕忘記故於此紀錄，另可參照 2013 年的&lt;a href=&#34;http://2or3.blogspot.com/2013/12/mvc5-aspnet-identity.html&#34;&gt;這篇&lt;/a&gt;及官方文檔。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;專案開啟後，我們在 Model 新增 ADO.net 實體資料模型&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/oSsVE3o.webp&#34;
  alt=&#34;&#34;width=&#34;824&#34; height=&#34;531&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/WKo3Sd5.webp&#34;
  alt=&#34;&#34;width=&#34;938&#34; height=&#34;655&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著有分為幾種方法，主要是從資料庫產生模型，和資料庫產生 Code First 的模型，可參見 &lt;a href=&#34;http://kevintsengtw.blogspot.com/2013/10/aspnet-mvc-entity-framework-code-first.html&#34;&gt;ASP.NET MVC 使用 Entity Framework Code First - 基礎入門&lt;/a&gt; 裡的說明。這篇會使用來自資料庫的 EF Designer 模型這個選項做建置。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/XkWWJzn.webp&#34;
  alt=&#34;&#34;width=&#34;612&#34; height=&#34;561&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;選擇了之後就會進到連線資料庫的部分，按照指示一步一步連線到資料庫。這邊也會幫你將連線字串儲存到 Web.config 裡。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/bU52Zv0.webp&#34;
  alt=&#34;&#34;width=&#34;617&#34; height=&#34;561&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著選取我們在這個模型中要包含的資料表。要注意下半部分的複數化或單數化建議是不要勾選，否則它會幫你把資料庫那些順手改個名，出事的機率會很高。&lt;/p&gt;
&lt;p&gt;成功的話就可以看見資料表出現在 edmx 檔的視窗裡了：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/QTld12h.webp&#34;
  alt=&#34;&#34;width=&#34;333&#34; height=&#34;309&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著我們來建立 Controller ：&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/d3s6lSO.webp&#34;
  alt=&#34;&#34;width=&#34;776&#34; height=&#34;318&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/n810bKz.webp&#34;
  alt=&#34;&#34;width=&#34;707&#34; height=&#34;611&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;上一篇我們為了從頭開始因此選擇空的 Controller，這邊則可以按照要求選擇自己需要的就可以了。我們選擇使用 EF 建立 Controller 和 View。&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/FsiSn3w.webp&#34;
  alt=&#34;&#34;width=&#34;585&#34; height=&#34;375&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;接著這邊選擇要進行操作的那個類，以及資料連接的字串，就能看到它開始從模型產生囉！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;註：如果在這步驟發生錯誤，先嘗試重建方案看看。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;如果一切順利，應該能看見它已經幫你建立好具有 列表、新增、修改、刪除和查詢的 Action 和 View&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/GEodVlL.webp&#34;
  alt=&#34;&#34;width=&#34;1098&#34; height=&#34;596&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;測試時基本功能也能順利地使用&lt;/p&gt;
&lt;p&gt;


&lt;img
  src=&#34;https://image.igouist.net/5SGg5PJ.webp&#34;
  alt=&#34;&#34;width=&#34;913&#34; height=&#34;374&#34;loading=&#34;auto&#34; decoding=&#34;async&#34;&gt;
&lt;/p&gt;
&lt;p&gt;心得：利用這個方式可以快速地產生具有基本操作功能的網頁，以此為基礎進行修改的話就能省下非常多的時間，可以說是非常強大的。但實際上其細節有非常多的地方可以處理，還是要更深的理解才能完全發揮呢。&lt;/p&gt;</description>
    </item>
    
  </channel>
</rss>