筆記目錄

Skip to content

在 .NET Framework 裡,有關 Web.config (App.config) 的應用

依不同組建組態發佈不同的設定檔

正常來說,本機開發、測試環境以及正式環境的 Web.config (App.config) 設定都會不一樣,如果每次更新程式時,都要將設定改為對應環境的設定值,過於麻煩;如果選擇更新程式不更新 Web.config (App.config),又有可能會造成有新增或調整設定時,忘記調整測試/開發環境上的設定檔內容。

有一種作法是每次更新程式都覆蓋設定檔,但是在發佈更新程式時,會直接產製對應環境的設定檔。

組建組態

如果有需要依照不同的設定來建置專案時,會需要設定組建組態。 不同的設定涵蓋產出不同的設定檔、編譯選項或依不同的專案常數產出不同的編譯結果,詳情請參閱「了解組建組態」。

預設組建組態有 debugrelease,可從 IDE 上進行切換。

vs build configuration menu

Web 專案發佈

有關專案發佈的部分,這邊僅針對資料夾發佈的部分進行說明,其他發佈方式我也沒玩過,如果有需要,請參閱「快速入門:發佈 ASP.NET Web 應用程式」。

新增發佈設定檔

  1. 對專案點選右鍵按「發佈」。

    vs project publish menu

  2. 點擊「新增發佈設定」。

    vs publish new profile

  3. 這篇文章只講資料夾發佈,所以選擇資料夾。

    vs publish target folder

  4. 設定輸出資料夾,我習慣上會設定成「bin\Publish{組態名稱}」,這邊設定成「bin\Publish\Release」。

    vs publish location setting

  5. 後續新增完建議更改發佈檔名稱,建議和組態名稱一致或有簡易說明。

    vs publish profile rename

  6. 編輯發佈設定,基本的設定可以使用 UI 編輯,這邊我會全打勾,實務上請依需求調整,像是如果是使用發佈到網站,又設定成「發佈前刪除所有的檔案」會造成網站上的儲存的暫存檔案(靘? 上傳的檔案)都被一併刪除。

    vs publish profile settings

  7. 可以直接修改 XML 變更發佈的設定,例如增加<ExcludeFilesFromDeployment>packages.config;Scripts\_references.js</ExcludeFilesFromDeployment>,使之在發佈時,排除特定檔案,至於 XML 具體有哪些發佈設定,目前並無在 MSDN 上查到相關說明。

    vs publish profile preview

TIP

當建立發佈設定檔時,會再產生一個名為「{Profile}.pubxml.user」的檔案,此檔案不知用途,實際看起來比較像是發佈紀錄,但這就和 MSDN 上說明不同,總之這個檔案是不該進入版控,實際上 Visual 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 檔案。

vs add config transform

  • 對著「Web.{組態名稱}.config」點右鍵,選擇「預覽和轉換」,可以查看轉換結果。

vs preview transform

  • Transform 的替換機制。 Transform 是用 XML 結構來定位要替換內容,xdt:Transform 屬性設定替換方式,使用以下範例內容進行解說。

    xml
    <a>
      <b>
        <c>
          <e name="e1" value="v1"></e>
          <e name="e2" value="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 轉換語法」。

發佈

選擇發佈後,會在目標資料夾看到輸出後的檔案,此時會發現產出的「Web.config」是合併後的內容。

vs publish output

範例

原本 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。

vs slowcheetah add transform

如果 NuGet 未安裝「Microsoft.VisualStudio.SlowCheetah」,會提醒並自動安裝。

vs slowcheetah nuget prompt

TIP

其實 Visual Studio 還有另外一個擴充套件 「ConfigTransformation」可支援 Config Transform,且不需要在專案上額外安裝 NuGet 套件,但 Visual Studio 2022 並不支援此擴充套件,且只能針對完整 Config 結構(根節點為 <configuration />)的 XML 進行轉換。

建置

「.NET Framework」的非 Web 專案的發佈是使用 ClickOnce,無法像 Web一樣用一般的發佈到資料夾,但是它的建置會觸發 Config Transform,所以選擇要發佈的組態設定後,建置專案即可,建議建置專案前,先刪除「bin」資料夾。

vs build non web project

範例

原本的 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 產出的稱為外部組態設定檔。

vs solution explorer structure

設定 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 專案選擇加入現有專案。

vs add existing item menu

使用加入連結的方式來新增外部設定檔及相關的外部組態設定檔。

vs add as link

  • 開啟專案檔(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」來外部引用外部設定檔。
    • configSourcefile 屬性的差別是後者允許增加其他設定做合併。

    • <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>

TIP

<None /> 節點和 <Content />,分別對應建置行為的「None(無)」和「Content(內容)」,其中「Content(內容)」是給 Web 使用的,詳情請參閱「建置動作值」。

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>

TIP

理論上<location /> 寫在 <system.web /> 外就夠了,但如果有需要,也可以加在其他節點外,但如果專案有使用 Entity Framework 的 Database First,那千萬不要寫在 <connectionStrings /> 外層,否則會造成 Entity Framework 的 UI 無法讀到已設定的連線字串(又是這個問題 orz)。

相關連結

異動歷程

  • 2022-11-10 初版文件建立。