講解 Subversion 分支與合併:以 TortoiseSVN 為例

在實務的版本控管情境中,套用分支與合併應該是最方便不過的了,會用的人可以得到許多版本控管的好處,但不會用的人卻會因為不理解而害怕使用它,由於 Subversion 1.5 開始新增了 Merge Tracking 功能,可有效降低維護分支的負擔,所以能學會分支與合併的技巧對整個版本管理流程來說會有非常大的幫助,今天我就來講解幾個利用 TortoiseSVN 實際的操作分支與合併的例子。

如下圖是一個很常見的分支示意圖,當你想在 trunk 發展一個新功能時,你可以透過 svn copy 的方式將整個 /calc/trunk 廉價複製/calc/branches/my-calc-branch,當 /calc/branches/my-calc-branch 分支完成所有新功能開發後,再將所有變更一次合併回 /calc/trunk 開發主線。

我講的 svn copy 其實就是對應到 TortoiseSVN 的 Branch/tag 功能

 

使用時應注意以下:

  1. 你要廉價複製的來源路徑
  2. 你要廉價複製的目的路徑 (「複製」就等同於「分支」,這是 SVN 附加上去的意義 )
  3. 寫下你為什麼要建立這個分支、用途是什麼,這訊息可以給團隊成員或版本經理看
  4. 我們可以在執行 Copy (Branch/tag) 的同時順便將你的「工作目錄」切換至「分支的路徑」

這時我們可以看一下 trunk 這個目錄的屬性,並切換至 Subversion 頁籤,你會發現該「工作目錄」所對應到的 Subversion 路徑已經變更到 /calc/branches/my-calc-branch 了。

但為了怕初學者混淆,我還是先將 /calc/trunk 切換回原本的 SVN 路徑:

這裡有幾點注意:

  1. 這裡是你的「工作目錄」(Working Copy)
  2. 這裡才是你要將「工作目錄」切換到的 SVN 路徑,也就是將 /calc/branches/my-calc-branch 切換至 /calc/trunk 路徑

這時你便可以開始在 /calc/branches/my-calc-branch 發展你這次想要新增的功能,且不會影響到其他團隊成員正在發展或維護的 /calc/trunk 開發主線。

也許過了一段時間,原本的 /calc/trunk 開發主線可能已經有其他團隊成員陸續修正了一些 Bugs,但這時你的分支 /calc/branches/my-calc-branch 就可以直接套用 開發主線 ( /calc/trunk ) 的更新,除了避免重複的工作外 (重複除錯),也可以避免版本的衝突發生,因為兩個人改同一個已知的 Bug 可能會因為用不同方法除錯或命名的方式不一致而發生衝突。

經常將 開發主線 ( /calc/trunk ) 的變更透過 svn merge 合併至 分支線路 ( /calc/branches/my-calc-branch ) 是一個非常好的習慣,這樣才不會讓你因為脫離 開發主線(trunk) 過久而導致將 分支線路 ( /calc/branches/my-calc-branch ) 合併回 開發主線 ( /calc/trunk ) 時發生許多版本衝突。

 

開發主線 ( /calc/trunk ) 合併至 分支線路 ( /calc/branches/my-calc-branch ) 通常選第 1 個,也就是 [Merge a range of revisions]

在 Merge 的視窗有以下注意事項:

  1. 設定要合併的來源,由於我們打算從 開發主線 ( /calc/trunk ) 合併分支這段時間所產生的變更至 分支線路 ( /calc/branches/my-calc-branch ),所以合併的來源要選擇 /calc/trunk 才對!
  2. 合併的結果會直接與目前「工作目錄」(Working Copy) 做比對,並修改目前工作目錄中的所有檔案。因此建議在做合併之前可以將所有尚未 commit 的檔案先 commit 到版本庫,避免不必要的衝突事件發生。

在正式進行合併(Merge)之前,建議先執行 Test merge 看看是否會發生什麼事!

若無異狀則可直接按下 [Merge] 按鈕進行合併動作,這時從 開發主線 ( /calc/trunk ) 分支出來的到目前工作目錄的版本就會做個比較,然後直接套用變更到你現有的檔案、目錄或屬性裡。

在合併之後如果沒有發生衝突,不代表真的沒衝突,所以必須再次對原始碼做出驗證後才能 commit 進版本庫,建議可參考以下流程:

  1. 將專案進行建置(Build)
  2. 如果沒問題再對專案進行單元測試(Unit Testing)或手動測試(Manual Testing)
  3. 如果都沒問題再 commit 目前合併無誤的版本到版本庫!

最後我們的 my-calc-branch 分支已經將新功能開發完成且測試無誤,所以要將 分支線路 ( /calc/branches/my-calc-branch ) 的最終版本合併回 開發主線 ( /calc/trunk ),這時的手續如下:

分支線路 ( /calc/branches/my-calc-branch ) 合併回 開發主線 ( /calc/trunk ) 通常選第 2 個,而特別選擇 [Reintegrate a branch] 這個選項是很重要的,因為這有以下好處:

  1. 讓 Subversion 能知道 開發主線 ( /calc/trunk ) 是從哪個分支、哪些版本合併進來的
  2. 有效節省 Subversion Repository (SVN儲存庫) 的空間,因為不用重複儲存分支的所有變更資訊
  3. 可以產生 Revision graph 得知專案開發的分支狀況

一樣可以先 測試合併(Test merge) 再正式進行 合併(Merge)

合併完後再將變更 commit 到版本庫

 

 

分支線路 ( /calc/branches/my-calc-branch ) 合併回 開發主線 ( /calc/trunk ) 並 commit 了之後,該分支就沒用了,Subversion 也不會繼續追蹤這個分支的變更 (因為之前已經 Reintegrate 過了),所以建議將該分支刪除。

刪除後要記得 commit 才能將「刪除動作」寫入到 Subversion Repository (SVN儲存庫) 中

 

以上就是一個 Subversion 分支與合併的完整過程,但有許多很細部的觀念我很難在一篇文章裡講清楚,如有時間建議多閱讀 Subversion 線上文件,當你建立起更多更完整的觀念後,對 Subversion 甚至是其他版本管理工具應該都能夠更加得心應手!

相關連結

  

此文章由 will 發表於 2010/1/29 上午 12:33:25

永久連結 | 評論 (7) | 此文章的RSSRSS comment feed |

分類: Subversion

標籤: , , , , , , ,

收藏:

在 ASP.NET 環境下使用 Memcached 快速上手指南

之前一直想研究 Memcached,這幾天花了些時間研究 Memcached Providers 好讓我現有的 ASP.NET 專案能解決多台主機間快取不同步的狀況, 想不到花沒多少時間就上手了,也因此做了一些記錄。

安裝 Memcached (ver 1.4.4) for Win32

1. 下載 memcached 1.4.4 Windows 32-bit binarymemcached Windows 64-bit pre-release

2. 在 C:\Program Files 建立一個 memcached 目錄

3. 將下載的壓縮檔解壓縮至 C:\Program Files\memcached 目錄

4. 開啟命令提示字元

5. 將 memcached 註冊進 Windows 服務

"C:\Program Files\memcached\memcached.exe" -d install

6. 啟動 memcached 服務

"C:\Program Files\memcached\memcached.exe" -d start

 

移除 Memcached (ver 1.4.4) for Win32

1. 開啟命令提示字元

2. 停止 memcached 服務

"C:\Program Files\memcached\memcached.exe" -d stop

3. 將 memcached 服務從 Windows 服務中移除

"C:\Program Files\memcached\memcached.exe" -d uninstall

4. 移除 C:\Program Files\memcached 目錄

 

測試 memcached 是否正常運作

1. 透過 telnet 指令連接到 localhost 的 11211 port, 其中 Port 11211 為 memcached 預設的 Listen Port,如果有開啟防火牆記得要設定才能讓遠端連接。( 預設會 Listen 所有 interface )

telnet localhost 11211

2. 輸入 stats 指令,並按下 Enter 取得目前 memcached 服務的運作狀態,有資料就代表安裝成功了

3. 輸入 quit 指令,並按下 Enter 退出

備註:完整指令請參考 memcached protocol

 

設定 ASP.NET 專案

1. 下載 Memcached Providers 組件 ==>  Memcached Providers 1.2 (.NET 3.5)

2. 將以下組件複製到 ASP.NET 網站的 bin 目錄下,或透過 加入參考(Add Reference) 方式將加入專案

  • Enyim.Caching.dll
  • Enyim.Caching.pdb
  • MemcachedProviders.dll
  • MemcachedProviders.pdb

 

設定 Memcached Cached Provider 並註冊至 web.config

1. 在 <configSections> 區段加入以下定義

<!-- Memcached -->
<section name="cacheProvider"
type="MemcachedProviders.Cache.CacheProviderSection, MemcachedProviders"
allowDefinition="MachineToApplication" restartOnExternalChanges="true"/>

<sectionGroup name="enyim.com">
<section name="memcached"
type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching" />
</sectionGroup>

2. 然後在 <appSettings> 之上加入以下定義

<enyim.com>
<memcached>
<servers>
<add address="127.0.0.1" port="11211" />
</servers>
<socketPool minPoolSize="10" maxPoolSize="100"
connectionTimeout="00:00:10" deadTimeout="00:02:00" />
</memcached>
</enyim.com>
<cacheProvider defaultProvider="MemcachedCacheProvider">
<providers>
<add name="MemcachedCacheProvider"
type="MemcachedProviders.Cache.MemcachedCacheProvider, MemcachedProviders"
keySuffix="_MyProjectName_" defaultExpireTime="2000"/>
</providers>
</cacheProvider>

 

開始使用 memcached 的 API

1. 匯入 MemcachedProviders.Cache 命名空間

using MemcachedProviders.Cache;

2. 取得 Cache 項目

object objCache = DistCache.Get(cacheKey);

3. 寫入 Cache 項目

// 寫入快取資料 (預設過期時間)
DistCache.Add(cacheKey, cacheValue);
// 快取 60 秒
DistCache.Add(cacheKey, cacheValue, 60 * 1000);
// 快取至今天結束
DistCache.Add(cacheKey, cacheValue, DateTime.Today.AddDays(1) - DateTime.Now);

4. 移除 Cache 項目

DistCache.Remove(cacheKey);

5. 移除所有 Cache 項目

DistCache.RemoveAll();

 

心得總結 (優點)

  • 架構簡單、容易上手
  • API 與 ASP.NET 快取機制類似,將現有的 HttpRuntime.Cache 替換成 DisCache 也很容易,像我大約只花半天就將一個不小的專案從 ASP.NET 快取轉移至 memcached 快取
  • 安裝部署容易
  • 很容易擴充記憶體快取的總量,增加 memcached 伺服器並修改 web.config 即可!
  • 超高效能,同時支援 TCP 與 UDP 協定
  • 跨平台、跨語言、開放協定、開放原始碼、許多大網站都使用 memcached
  • 內建提供 Session Provider

心得總結 (缺點)

  • 無法取得所有快取項目,用 ASP.NET 快取可以透過 Cache.GetEnumerator() 取得所有快取項目 ( 參考: ASP.NET 如何將目前的 Cache 物件全部清空 ),但在 memcached 完全沒辦法,因為 memcached protocol 根本沒有定義這個功能,除非你自行實做。
  • 網路上可下載的 memcached 版本都不支援 高可用性(High Availability; HA) 特性
  • AppFabric Caching (Velocity) 相比功能少很多
  • 缺乏中文資源、文件少、中文社群不積極、有 Bug 不見得有人理 (還好是開源碼可以自己改)

 

相關連結

  

此文章由 will 發表於 2010/1/27 下午 09:09:01

永久連結 | 評論 (9) | 此文章的RSSRSS comment feed |

分類: ASP.NET | ASP.NET MVC | .Net

標籤: , , , ,

收藏:

如何看懂 Microsoft Open License Agreement 授權協議書

前陣子大量採購了一堆微軟產品,不過剛拿到授權書時卻傻眼,在上面同一套產品重複出現好多次,然後分什麼 Key Classification 與 Key Type,然後產品金鑰好多組,那我安裝軟體時到底應該輸入哪一組序號才對呢?經過與微軟技術支援中心討論一番後才清楚,不過裡面有些產品授權的縮略字(acronym) 連他們也不太清楚,甚至沒有官方文件可查,我花了好幾週的時間抽空研究,心得頗為豐富,但我研究那麼多買微軟產品也不會比較便宜就是了 ^^

首先,我買了 6 套微軟的產品,收到的授權書共三頁,如下圖:

第一頁

 

第二頁

第三頁

關於 Volume License Product Key Information 的表格各欄位說明

  • Product Description
    授權的產品名稱
  • Key Classification
    金鑰分類
    • 由於購買 Windows Server 2008 的授權是允許「降級使用」的,也就是你雖然買了 Windows Server 2008 的授權,在授權書上也會一併提供你 Windows Server 2003 的產品金鑰,所以可以讓在不同 Windows 版本之間使用不同的金鑰啟動產品,但你同時間只能選擇一個 Windows 版本使用!
  • VLK
    大量授權啟動金鑰
  • Key Type
    金鑰類型
    • 欄位為空值:代表這是透過手動啟動所使用的金鑰 (MAK)
    • VA1.0 代表 Volumen Activation 1.0,Windows Server 2003 之前的版本都是 VA1.0
    • KMS 代表這序號要輸入到 Key Management Service 中,產品可透過 KMS 服務啟動
      ( KMS 啟動需安裝 Activation Host 才能用 )

重要縮略字名詞解說 (這我研究最久…)

  • MAK = Manual Activation Key (手動啟動金鑰) = Multiple Activation Key (多重啟動金鑰)
    註1: 所有金鑰都可以「多重啟動」的,所以我覺得這裡應該講的是 Manual Activation Key 才對!
    註2: 有些繁中的文件會翻譯成「多重啟動金鑰」,但國外有些文件寫 Manual Activation Key !!
  • KMS = Key Management Service (金鑰管理服務)
  • VA = Volumne Activation (大量啟動)
  • VLK = Volume License Key (大量啟動金鑰)
  • SA = Software Assurance (軟體保證) (保證在購買後 2 年內有新版本產品出現時可以免費升級)
  • LicSAPk = Lic/SA Pack = License and Software Assurance (同時購買軟體授權與軟體保證授權)
  • SNGL = Single Language (單一語系)
  • ALNG = All Language (所有語系)
  • OLP = Open License Program (適用 Open License 購買的產品)
  • NL = No Level (不分等級的授權)
  • CAL = Client Access License (用戶端存取授權)
  • FPP = Full Packaged Product (完整產品包裝) (彩盒包裝)
  • PUP = Product Upgrade Pack (產品升級包裝)

其中 KMS A , MAK A, KMS B , MAK B , … 都是屬於「產品金鑰群組」,其中的 A , B , C 分別代表不同的產品類型產品等級,例如:

  • Windows Web Server 2008/ Windows Server 2008 HPC Edition (MAK/KMS A)
  • Windows Server 2008 Standard/ Windows Server 2008 Enterprise (MAK/KMS B)
  • Windows Server 2008 Datacenter/ Windows Server 2008 Itanium-Based Systems (MAK/KMS C)

也就是說 MAK A 的金鑰,無法用來啟用 MAK B 或 MAK C 等級的產品。

何謂 NL ?

NL 是 No Level 的縮寫,微軟有些大型客戶可能會在採購某些產品時一次採購很大量的產品,而這裡的 Level 指的是 Discount Level (折扣等級),當大咖購買大量授權的時候就有可能用較高 Level 的價格採購 ( 例如: Level C ),像我們這種小咖就只能買 NL 的產品。 [ 參考: What is OLP NL in server licenses? ]

何謂 MAK ?

依據以下解釋,我還是覺得 MAK 的全名是 Manual Activation Key 才對,台灣微軟可能翻錯了,或是故意翻譯成「多重啟動金鑰」比較容易理解?那就不得而知了。

A MAK is used for one-time activation of a computer with Microsoft hosted activation services. There are two ways to activate computers using a MAK:

  • MAK Independent Activation requires each computer to independently connect and activate with Microsoft, either over the Internet or by telephone.
  • With MAK Proxy Activation, a computer acting as the MAK proxy gathers activation information from multiple computers on the network and then sends a centralized activation request to Microsoft hosted activation services on their behalf. A free application, the Volume Activation Management Tool (VAMT), enables you to do a MAK Proxy Activation.

何謂 VLK ?

Volume License Keys (VLK), including MAK and KMS, are issued to you under a specific license agreement and enable your organization to use the software and products that you have licensed.

註: 我們這次收到的授權書就是 VLK,所以同時會給我們 MAK 與 KMS 的金鑰。

何謂 KMS ?  ( 通常針對較大量用戶端的情況才需要 KMS,我沒用過,所以僅留下原文說明 )

KMS is a lightweight service that does not require a dedicated system and can easily be co-hosted on a system that provides other services. With KMS, you can complete activations on your local network, eliminating the need for individual computers to connect to Microsoft for product activation.

A KMS key is used only to activate the KMS host with a Microsoft activation server. KMS requires a minimum number of computers in a network environment. You must have at least five (5) computers to activate computers that are running Windows Server 2008 or Windows Server 2008 R2, and at least twenty-five (25) computers to activate computers that are running Windows Vista or Windows 7. These minimums, called activation thresholds, are set so that they are easily met by enterprise customers. After you set up your KMS activation, by default, both physical and virtual Windows Vista and Windows Server 2008 computers will try to activate by connecting to the KMS host. For more information about activation thresholds, see the Volume Activation Planning Guide.

Are there usage limits on KMS keys?

Yes. A KMS key can activate six KMS hosts with up to 10 activations per host. If you need more activations for your KMS key, you can call your Microsoft Activation Center to request an increase. There is no limit to how many KMS clients can be activated with the KMS host.

Note that a KMS key is used only to activate the KMS host with a Microsoft activation server.

 

相關連結

  

此文章由 will 發表於 2010/1/26 下午 06:24:51

永久連結 | 評論 (2) | 此文章的RSSRSS comment feed |

分類: Tips | 心得分享

標籤: , ,

收藏:

如何移除遠端桌面登入時的背景圖片以加速登入程序

我有個客戶購買了壹台 DELL 的伺服器,由於主機在美國,我每次透過 遠端桌面(Remote Desktop) 登入時都要等個 25 秒才能登入成功,其中 20 秒再等 DELL 提供的超大預設桌布顯示完畢畫面才能出現桌面。還好找到方法可以透過修改機碼將該預設桌面移除,馬上省去 20 秒的等待時間。

這個問題主要出現在剛連線的時候,只要遠端桌面連線上去背景就會先顯示「預設背景圖片」,在正常的 Windows 安裝下是不會有所謂「預設背景圖片」的,但是透過 OEM 出貨的機器 ( 例如: DELL ) 就有可能內建預設背景圖片,而導致在「尚未登入」時就會先顯示背景圖片,以下圖示是我登入時的畫面,畫面一格一格的顯示出來,頻寬如果不夠將會非常非常慢:

而且該桌面背景圖片還超大的,解析度非常高,有 1.8MB 這麼大:

移除的方式要透過 regedit 工具,要先找到以下機碼

HKEY_USERS\.DEFAULT\Control Panel\Desktop

該機碼裡有個 Wallpaper 字串值,將該字串值的內容清空即可!

HKEY_USERS\.DEFAULT\Control Panel\Desktop

下次再登入就飛快了! ^_^

相關連結

  

此文章由 will 發表於 2010/1/25 下午 02:52:29

永久連結 | 評論 (2) | 此文章的RSSRSS comment feed |

分類: 系統管理 | Tips

標籤: ,

收藏:

如何讓 IIS6 / IIS7 中同站台不同應用程式間共用 Session 資料

在 Web Farm 環境下部署網站需要關注的細節可不少,在部署大型網站的時候 IIS 這部分到底要算 IT 的領域還是開發人員(Developer)的領域其實分不太清楚,像要在「同一個站台」區分「不同應用程式」且還要能讓 Session 彼此互通,這到底應該歸誰管呢?這可不是用「ASP.NET 開發伺服器」可以模擬出來的,而 IT 人員如果不會寫程式應該也不知該如何是好,這也是我認為 ASP.NET 開發人員應該多熟悉 IIS 的原因。

要在 Web Farm 環境下要達成 Session 互通有以下條件:

  1. 確保 <machineKey> 要設定一致
  2. 確保各站台在 IIS 中 metabase 定義的「應用程式路徑」必須一致
  3. 使用者的 Session Cookie 的 名稱/值 必須一致 (不能跨越不同的網域網址不同的父網域網址)

這三個條件是我從四處的文件與實際經驗累積而來的結果,而其中最不容易發現的是「應用程式路徑」必須一致這個條件。

例如在 IIS 中,預設站台的 ID 為 1,在站台根目錄這個預設用程式的「應用程式路徑」為:

/LM/W3SVC/1/ROOT

若你在 Web Farm 環境下的第二台伺服器,除了原本的預設站台外,再建立另一個新的站台,則該站台的 ID 為 2,其「應用程式路徑」為:

/LM/W3SVC/2/ROOT

那麼這兩個站台便無法共用 Session,即便 Session 儲存後端用 SQL ServerASP.NET 狀態服務 且也設定好相同的 <machineKey> 也一樣無法互通 Session 資料。

基於這個規則,在架設 Web Farm 網站時就必須特別小心設定,否則可能查設定查到天荒地老也不知道為什麼會這樣,但在不太複雜的網站下,通常多台 Web Farm 主機下也各別只會有一個站台,所以也不一定會遇到這狀況。

那麼在「同一個站台」下「不同應用程式」時,其實就是「兩個應用程式」,我們可以利用 appcmd 指令列工具測試一下:

C:\Windows\System32\inetsrv>appcmd list app
APP "Default Web Site/" (applicationPool:DefaultAppPool)
APP "Default Web Site/WebSite2" (applicationPool:DefaultAppPool)

以上述為例,在 Default Web Site 站台下另外新增了一個「應用程式」為 /WebSite2,其「應用程式路徑」分別為:

/LM/W3SVC/1/ROOT
/LM/W3SVC/1/ROOT/WebSite2

所以這兩個應用程式基本上是無法互通 Session 的,但你可能會納悶在同一個站台下為何 Session 無法互通,因為在 .NET Framework 裡的 System.Web.SessionState 命名空間已經預先設定了取得 Session 資料的邏輯,所以這是 by design 的情況,程式碼並無法修改。

 

這些屬性無法透過「正規」的管道進行修正或調整,所以「表面上」似乎無解,除非你自行撰寫新的 Session 機制並將 IIS 中內建的 Session Module 抽換掉,但這實在太累人了,沒必要重新發明輪子。

還好在 .NET 有 Reflection (反映) 機制,透過自訂的 HttpModule 可以讓 SessionStateModule 模組被載入之前動態將 System.Web.HttpRuntime 型別中的 _appDomainAppId 的值換成我們希望的值,這樣就可以讓不同應用程式之間共用 Session 資料了,程式碼如下:

FieldInfo runtimeInfo = typeof(HttpRuntime).GetField("_theRuntime", 
BindingFlags.Static | BindingFlags.NonPublic);

HttpRuntime theRuntime = (HttpRuntime)runtimeInfo.GetValue(null);

FieldInfo appDomainAppIdInfo = typeof(HttpRuntime).GetField("_appDomainAppId",
BindingFlags.Instance | BindingFlags.NonPublic);

appDomainAppIdInfo.SetValue(theRuntime, "SharedAppDomainAppId");

假設我們這個 HttpModule 的類別名稱為 SharedSessionModule 並放置在 App_Code 動態編譯目錄下,我們就可以修改 web.config 將我們自訂的 SharedSessionModule 註冊到 IIS6 的 <httpModules> 區段,或 IIS7 的 <modules> 區段中。

IIS6

<httpModules>
<add name="SharedSessionModule" type="SharedSessionModule, App_Code"/>
</httpModules>

IIS7

<modules>
<remove name="Session" />
<add name="SharedSessionModule" type="SharedSessionModule, App_Code"/>
<add name="Session1" type="System.Web.SessionState.SessionStateModule,
System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
/>
</modules>

注意: 在 IIS7 中由於 整合管線(Integrated Pipeline) 的關係 HttpModule 的載入順序有些應該注意的地方,你不能使用在 applicationHost.config 中已經定義過的模組名稱,否則將無法調整載入模組的順序,所以才必須「先移除 Session」再「載入 SharedSessionModule」然後再「重新載入 SessionStateModule 並命名為 Session1」才行。如果預設的 SessionStateModule 模組在 SharedSessionModule 之前先執行,在 SessionStateModule 模組完成初始化動作後 SharedSessionModule 再做任何修改就沒有作用了。

相關連結

  

此文章由 will 發表於 2010/1/24 下午 02:02:10

永久連結 | 評論 (7) | 此文章的RSSRSS comment feed |

分類: Web | ASP.NET | IIS

標籤: , , , ,

收藏: