# .NET Framework 裡,有關 Web.config (App.config) 的應用 [](https://hackmd.io/K0ALoDQWSSKmWy5jMWEjPA) ## 依不同組建組態發佈不同的設定檔 正常來說,本機開發、測試環境以及正式環境的 Web.config (App.config) 設定都會不一樣,如果每次更新程式時,都要將設定改為對應環境的設定值,過於麻煩;如果選擇更新程式不更新 Web.config (App.config),又有可能會造成有新增或調整設定時,忘記調整測試/開發環境上的設定檔內容。 有一種作法是每次更新程式都覆蓋設定檔,但是在發佈更新程式時,會直接產製對應環境的設定檔。 ### 組建組態 如果有需要依照不同的設定來建置專案時,會需要設定組建組態。 不同的設定涵蓋產出不同的設定檔、編譯選項或依不同的專案常數產出不同的編譯結果,詳情請參閱「[了解組建組態](https://learn.microsoft.com/zh-tw/visualstudio/ide/understanding-build-configurations?view=vs-2022)」。 預設組建組態有 `debug` 和 `release`,可從 IDE 上進行切換。  ### Web 專案發佈 有關專案發佈的部分,這邊僅針對資料夾發佈的部分進行說明,其他發佈方式我也沒玩過,如果有需要,請參閱「[快速入門:發佈 ASP.NET Web 應用程式](https://learn.microsoft.com/zh-tw/visualstudio/deployment/quickstart-deploy-aspnet-web-app)」。 #### 新增發佈設定檔 1. 對專案點選右鍵按「發佈」。  2. 點擊「新增發佈設定」。  3. 這篇文章只講資料夾發佈,所以選擇資料夾。  4. 設定輸出資料夾,我習慣上會設定成「bin\Publish\{組態名稱}」,這邊設定成「bin\Publish\Release」。  5. 後續新增完建議更改發佈檔名稱,建議和組態名稱一致或有簡易說明。  6. 編輯發佈設定,基本的設定可以使用 UI 編輯,這邊我會全打勾,實務上請依需求調整,像是如果是使用發佈到網站,又設定成「發佈前刪除所有的檔案」會造成網站上的儲存的暫存檔案(e.g. 上傳的檔案)都被一併刪除。  7. 可以直接修改 XML 變更發佈的設定,例如增加`<ExcludeFilesFromDeployment>packages.config;Scripts\_references.js</ExcludeFilesFromDeployment>`,使之在發佈時,排除特定檔案,至於 XML 具體有哪些發佈設定,目前並無在 MSDN 上查到相關說明。  :::info 當建立發佈設定檔時,會再產生一個名為「{Profile}.pubxml.user」的檔案,此檔案不知用途,實際看起來比較像是發佈紀錄,但這就和 MSDN 上說明不同,總之這個檔案是不該進入版控,實際上 Viusal Studio 預設的「.gitignore」也會忽略附檔名為「.user」這類使用者設置檔。 ::: #### Web.config Transform 當建立一個新的 Web 專案時,會發現專案根目錄預設有三個 Config 檔案,「Web.config」、「Web.Debug.config」和「Web.Release.config」,但在建置專案時,會發現都是使用「Web.config」的設定,其餘兩個 Config 其實是在發佈時,用來轉換「Web.config」的部分內容。 * 當有發生缺漏組建組態對應的「Web.{組態名稱}.config」檔案時(有可能是誤刪除或是有新建其他組建組態),在「Web.config」上點右鍵,可以看到「新增設定轉換」可以點選,點選後就會建立缺漏的 Config 檔案。  * 對著「Web.{組態名稱}.config」點右鍵,選擇「預覽和轉換」,可以查看轉換結果。  * Transform 的替換機制。 Transform 是用 XML 結構來定位要替換內容,`xdt:Transform` 屬性設定替換方式,使用以下範例內容進行解說。 ```xml <a> <b> <c> <e name="e1" valu1="v1"></e> <e name="e2" valu1="v2"></e> </c> <d></d> </b> </a> ``` 如果要替換 `<d>` 節點的內容,就需要準備一個包含 `<d>` 節點及全部父節點的 XML,結構如下。 ```xml <a> <b> <d xdt:Transform="Replace"></d> </b> </a> ``` 而像`<e>` 這種同一節點底下,有多個相同節點名的,就需要使用屬性`xdt:Locator` 來定位,舉例來說,如果要替換`<e name="e1"></e>`的話,則 XML 內容為。 ```xml <a> <b> <c> <e name="e1" value="v11" xdt:Locator="Match(name)" xdt:Transform="SetAttributes"></e> </c> </b> </a> ``` * 常用的轉換設定 * 更換 `connectionStrings` ```xml <!--全部的 connectionStrings 都更換--> <connectionStrings xdt:Transform="Replace"> <add name="Default" connectionString="{connectionString}" providerName="System.Data.SqlClient" /> </connectionStrings> <!--只替換單一字串--> <add name="Default" connectionString="{connectionString}" providerName="System.Data.SqlClient" xdt:Locator="Match(name)" xdt:Transform="SetAttributes" /> ``` * 更換 `appSettings` ```xml <!--建議單獨替換,而不是整個 appSettings 都替換--> <add key="key" value="value1" xdt:Locator="Match(key)" xdt:Transform="SetAttributes" /> ``` * `system.web` 常見替換項目 ```xml <!--將 mode="Off" 改為 RemoteOnly,並新增 defaultRedirect --> <customErrors mode="RemoteOnly" xdt:Transform="SetAttributes" /> <!--將 enableVersionHeader 改為 false--> <httpRuntime enableVersionHeader="false" xdt:Transform="SetAttributes(enableVersionHeader)" /> <!--將 requireSSL 改為 true--> <httpCookies requireSSL="true" xdt:Transform="SetAttributes(requireSSL)" /> <!--移除 debug="true" 的設定--> <compilation xdt:Transform="RemoveAttributes(debug)" /> ``` 完整的 Transform 語法,可以參考「[使用 Visual Studio 部署 Web 項目的 Web.config 轉換語法](https://learn.microsoft.com/en-us/previous-versions/aspnet/dd465326(v=vs.110))」。 #### 發佈 選擇發佈後,會在目標資料夾看到輸出後的檔案,此時會發現產出的「Web.config」是合併後的內容。  #### 範例 原本 Web.config ```xml <?xml version="1.0" encoding="utf-8"?> <configuration> <connectionStrings> <add name="MyDB" connectionString="Data Source=SQLServer;Initial Catalog=MyDB;Integrated Security=True" /> </connectionStrings> <appSettings> <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="MyKey" value="MyValue" /> </appSettings> <system.web> <compilation debug="true" targetFramework="4.8.1" /> <customErrors mode="Off" /> <httpRuntime targetFramework="4.8.1" /> <httpCookies requireSSL="false" /> </system.web> <!--後面的沒異動,這邊範例省略--> </configuration> ``` Web.Release.config ```xml <?xml version="1.0" encoding="utf-8"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <connectionStrings> <add name="MyDB" connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" /> </connectionStrings> <appSettings> <add key="MyKey" value="MyReleaseValue" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" /> </appSettings> <system.web> <compilation xdt:Transform="RemoveAttributes(debug)" /> <customErrors mode="RemoteOnly" xdt:Transform="SetAttributes" /> <httpRuntime enableVersionHeader="false" xdt:Transform="SetAttributes(enableVersionHeader)" /> <!--將 requireSSL 改為 true--> <httpCookies requireSSL="true" xdt:Transform="SetAttributes(requireSSL)" /> </system.web> </configuration> ``` 發佈後的 Web.config ```xml <?xml version="1.0" encoding="utf-8"?> <configuration> <connectionStrings> <add name="MyDB" connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True" /> </connectionStrings> <appSettings> <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="MyKey" value="MyReleaseValue" /> </appSettings> <system.web> <compilation targetFramework="4.8.1" /> <customErrors mode="RemoteOnly" /> <httpRuntime targetFramework="4.8.1" enableVersionHeader="false" /> <httpCookies requireSSL="true" /> </system.web> <runtime> <!--後面的沒異動,這邊範例省略--> </configuration> ``` ### **非** Web 專案發佈 非 Web 專案預設並無支援「App.config」Transform,所以需要安裝其他套件處理。 #### 套件 * Visual Studio:在延伸模組安裝「SlowCheetah」。 * NuGet:在要使用 Config Transform 的專案上安裝「Microsoft.VisualStudio.SlowCheetah」。 當 Visual Studio 安裝「SlowCheetah」以後,非 Web 專案的 App.config 右鍵選單會增加一個 「Add Transform」的選項,選擇可以產生其他組態的 Config。  如果 NuGet 未安裝「Microsoft.VisualStudio.SlowCheetah」,會提醒並自動安裝。  :::info 其實 Visual Studio 還有另外一個擴充套件 「ConfigTransformation」可支援 Config Transform,且不需要在專案上額外安裝 NuGet 套件,但 Visual Studio 2022 並不支援此擴充套件,且只能針對完整 Config 結構(根節點為 `<configuration />`)的 XML 進行轉換。 ::: #### 建置 「.NET Framework」的非 Web 專案的發佈是使用 ClickOnce,無法像 Web一樣用一般的發佈到資料夾,但是它的建置會觸發 Config Transform,所以選擇要發佈的組態設定後,建置專案即可,建議建置專案前,先刪除「bin」資料夾。  #### 範例 原本的 App.config ```xml <?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings> <add name="MyDB" connectionString="Data Source=SQLServer;Initial Catalog=MyDB;Integrated Security=True" /> </connectionStrings> <appSettings> <add key="MyKey" value="MyValue" /> </appSettings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" /> </startup> </configuration> ``` App.Release.config ```xml <?xml version="1.0" encoding="utf-8"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <connectionStrings> <add name="MyDB" connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" /> </connectionStrings> <appSettings> <add key="MyKey" value="MyReleaseValue" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" /> </appSettings> </configuration> ``` 建置後的 {專案名稱}.exe.config ```xml <?xml version="1.0" encoding="utf-8"?> <configuration> <connectionStrings> <add name="MyDB" connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True" /> </connectionStrings> <appSettings> <add key="MyKey" value="MyReleaseValue" /> </appSettings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" /> </startup> </configuration> ``` ## 不同專案共用設定檔 如果整個方案底下有多個專案需要連同一個資料庫,例如有網站、多個排程程式或 Web API 等,而每當更換資料庫環境時,就需要全部的專案都進行修改,此時可以考慮將 `<connectionStrings />` 單獨放置一個檔案,來讓多個專案進行引用。 ### 設定步驟 以下建立一個專案 Library 專案,並新增「AppGlobal.config」、「Connection.config」和「Smtp.config」三個設定檔,三個設定檔都設定 Config Transform。 為了方便,後續會將這三個設定檔統稱為外部設定檔,Config Transform 產出生出來的稱為外部組態設定檔。  #### 設定 Config 內容 這邊以僅以 AppGlobal.config 為例。 AppGlobal.config ```xml <?xml version="1.0" encoding="utf-8" ?> <appSettings> <add key="Test" value="TestValue1" /> </appSettings> ``` ```xml <?xml version="1.0" encoding="utf-8" ?> <!--根節點必須包含屬性 xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" --> <appSettings xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <add key="Test" value="TestReleaseValue1" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" /> </appSettings> ``` #### 從 Web 專案加入引用 * 安裝「Microsoft.VisualStudio.SlowCheetah」來處理這些外部設定檔的轉換。 * Web 專案除了加入 Library 的專案參考 * 新建的三個 Config 及相關組態檔的檔案連結。 Web 專案選擇加入現有項目。  使用加入連結的方式來新增外部設定檔及相關的外部組態設定檔。  * 開啟專案檔(csproj)進行以下修改: * 增加 `<TransformOnBuild>true</TransformOnBuild>`,標註要建置時要執行 Config Transform。 * 外部設定檔增加 `<CopyToOutputDirectory>Always</CopyToOutputDirectory>`。 * 將外部組態設定檔改從 `<Content />` 節點改為 `<None />` 節點,避免發佈時,也同時被發佈出去。 * 外部組態設定檔增加 `<DependentUpon />` 節點,使之在 Visual Studio 上面是巢狀顯示在外部設定檔下方。 ```xml <Content Include="..\BuildConfigSample.Library\AppGlobal.config"> <Link>AppGlobal.config</Link> <TransformOnBuild>true</TransformOnBuild> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> <None Include="..\BuildConfigSample.Library\AppGlobal.Debug.config"> <Link>AppGlobal.Debug.config</Link> <DependentUpon>AppGlobal.config</DependentUpon> <TransformOnBuild>true</TransformOnBuild> </None> <None Include="..\BuildConfigSample.Library\AppGlobal.Release.config"> <Link>AppGlobal.Release.config</Link> <DependentUpon>AppGlobal.config</DependentUpon> <TransformOnBuild>true</TransformOnBuild> </None> <!--Connection.config、Smtp.config 改法一樣--> ``` * 修改「Web.config」和「Web.Release.config」來外部引用外部設定檔。 * `configSource` 和 `file` 屬性的差別是後者允許增加其他設定做合併。 * `<connectionStrings />` 和 `<smtp />` 皆不可使用 `file`。 * `<appSettings />` 允許使用 `configSource`,但建議使用 `file`。 * `<appSettings />` 如果外部引用設定檔的 `key`,與自己增加的 `key` 相同,會使用外部引用的值,所以無法藉由定義相同的 `key` 來覆蓋設定。 * 使用連結的方式加入設定檔,實際上專案根目錄不會有檔案,導致 Web 在執行 Debug 模式時,「Web.config」會無法在同層資料夾找到外部設定檔,所以改引用「bin」底下的檔案(前面在設定時,有將外部設定檔設定「複製到輸出目錄」)。 Web.config ```xml <configuration> <connectionStrings configSource="bin\Connection.config" /> <appSettings file="bin\AppGlobal.config"> <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="MyKey" value="MyValue" /> </appSettings> <system.net> <mailSettings> <smtp configSource="bin\Smtp.config" /> </mailSettings> </system.net> <!--後續內容省略--> </configuration> ``` Web.Release.config ```xml <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <connectionStrings configSource="Connection.config" xdt:Transform="SetAttributes" /> <appSettings file="AppGlobal.config" xdt:Transform="SetAttributes"> <add key="MyKey" value="MyReleaseValue" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" /> </appSettings> <system.net> <mailSettings> <smtp configSource="Smtp.config" xdt:Transform="Replace" /> </mailSettings> </system.net> <!--後續內容省略--> </configuration> ``` #### 從**非** Web 專案加入引用 前面操作步驟都和 Web 一樣,但修改專案檔和 App.config 的內容稍微有點不同。 專案檔裡的外部設定檔是使用 `<None />` 節點。 ```xml <None Include="..\BuildConfigSample.Library\AppGlobal.config"> <Link>AppGlobal.config</Link> <TransformOnBuild>true</TransformOnBuild> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> <None Include="..\BuildConfigSample.Library\AppGlobal.Debug.config"> <Link>AppGlobal.Debug.config</Link> <DependentUpon>AppGlobal.config</DependentUpon> <TransformOnBuild>true</TransformOnBuild> </None> <None Include="..\BuildConfigSample.Library\AppGlobal.Release.config"> <Link>AppGlobal.Release.config</Link> <DependentUpon>AppGlobal.config</DependentUpon> <TransformOnBuild>true</TransformOnBuild> </None> ``` :::info `<None />` 節點和 `<Content />`,分別對應建置行為的「None(無)」和「Content(內容)」,其中「Content(內容)」是給 Web 使用的,詳情請參閱「[建置動作值](https://learn.microsoft.com/zh-tw/visualstudio/ide/build-actions?view=vs-2022#build-action-values)」。 ::: ##### App.config 由於 Debug 模式,設定檔本身就是使用「bin\{組態名稱}」底下的 「{專案名稱}.exe.config」檔案,所以路徑不需要指定到「bin」底下。 ```xml <?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings configSource="Connection.config" /> <appSettings file="AppGlobal.config"> <add key="MyKey" value="MyValue" /> </appSettings> <system.net> <mailSettings> <smtp configSource="Smtp.config" /> </mailSettings> </system.net> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" /> </startup> </configuration> ``` ##### App.Release.config 改成外部引用後 App.Release.config 就不需要轉換 connectionStrings 內容。 ```xml <?xml version="1.0" encoding="utf-8"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <appSettings> <add key="MyKey" value="MyReleaseValue" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" /> </appSettings> </configuration> ``` :::warning 如果使用 Entity Framework 的 Database First,則該專案的 App.config 的連線字串不能使用外部引用檔的方式設定,否則 Entity Framework 的 UI 會找不到已經設定好的連線字串。 ::: ## 切斷 Web.config 的繼承關係 IIS 允許在網站底下建置子網站,此時子網站的 Web.config 會繼承父網站的設定,有時候會發生衝突,特別是在兩邊的 Framework 版本有差異的情況下,此時可以在父網站使用 `<location>` 來切斷繼承,寫法如下: ```xml <location path="." inheritInChildApplications="false"> <system.web> <!--...內部程式碼...--> </system.web> </location> ``` :::info 理論上`<location />` 寫在 `<system.web />` 外就夠了,但如果有需要,也可以加在其他節點外,但如果專案有使用 Entity Framework 的 Database First,那千萬不要寫在 `<connectionStrings />` 外層,反則會造成 Entity Framework 的 UI 無法讀到已設定的連線字串(又是這個問題 orz)。 ::: ### 相關連結 * [如何讓同一個 IIS 站台設定兩個不同的 ASP.NET 應用程式](https://blog.miniasp.com/post/2008/06/15/Making-child-ASPNET-applications-work) ###### tags: `.NET` `.NET Framework`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up