﻿# 如何啟用 ASP.NET Core 6.0 部署到 IIS 的陰影複製 (Shadow-copying) 功能

ASP.NET Core 可以部署至 IIS 網站伺服器，但一直以來都有一個毛病，那就是 ASP.NET Core 網站在運行的過程中，許多 `*.dll` 檔案會被鎖定，導致這些檔案無法順利更新，必須暫時關閉應用程式集區才能順利對網站進行部署。從 ASP.NET Core 6.0 開始，[ASP.NET Core Module](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-6.0&WT.mc_id=DT-MVP-4015686) 包含了一項實驗性功能名叫 **陰影複製**(Shadow-copying)，這個功能可以徹底解決網站運行中無法部署程式的問題。

### 使用 app\_offline.htm 關閉網站

其實一直以來，微軟官方建議的更新手段，是在網站根目錄加上 [app\_offline.htm](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/app-offline?view=aspnetcore-6.0&WT.mc_id=DT-MVP-4015686) 檔案，強迫關閉應用程式集區。但是這種更新方法會導致網站在更新部署的過程中無法提供服務，以致於網站中斷服務，所以一直不是很理想的部署方法。

首先，當網站加上 [app\_offline.htm](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/app-offline?view=aspnetcore-6.0&WT.mc_id=DT-MVP-4015686) 檔案之後，**應用程式集區**會立即停止運作。注意，並不是「網站」被關閉，網站其實還在，只是整個網站無論用戶端開啟任何頁面，一律只提供 `app_offline.htm` 檔案的內容，且所有 HTTP 要求的 `Content-Type` 固定為 `text/html`，以及 HTTP 回應狀態碼為 `503 Service Unavailable`。

不過，即便你加上了 `app_offline.htm` 檔案，其實還要等 ASP.NET Core 的程式停止 (Graceful Shutdown)，所以這些 `*.dll` 檔案並**不會立即解除鎖定**，而檔案解鎖的時間可能會介於 1 ~ 30 秒不等，因此更新程式的時候，還需要額外實作 retry (重新嘗試) 機制，以確保 ASP.NET Core 程式可以順利更新。

### 使用 ASP.NET Core Module 的陰影複製 (Shadow-copying)

當我們使用 .NET SDK 的 `dotnet publish` 命令發行 ASP.NET Core 網站時，預設會自動產生一個給 IIS 站台專用的 `web.config` 檔案，其內容如下：

```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\WebApplication1.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>
```

要啟用 ASP.NET Core Module 的 **陰影複製** (Shadow-copying) 功能，必須調整 `<handlerSettings>` 設定如下：

```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\WebApplication1.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess">
        <handlerSettings>
          <handlerSetting name="experimentalEnableShadowCopy" value="true" />
          <handlerSetting name="shadowCopyDirectory" value="../ShadowCopyDirectory/" />
          <!-- Only enable handler logging if you encounter issues-->
          <!-- <handlerSetting name="debugFile" value="../ShadowCopyLogs/aspnetcore-debug.log" /> -->
          <!-- <handlerSetting name="debugLevel" value="FILE,TRACE" /> -->
        </handlerSettings>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>
```

> 注意: 請務必安裝 ASP.NET Core 6.0 的 [Hosting Bundle](https://dotnet.microsoft.com/download/dotnet/6.0?WT.mc_id=DT-MVP-4015686) 才能啟用**陰影複製**功能。

當**陰影複製**設定啟用之後，其運作原理如下：

1.  使用者發送 HTTP 要求到 IIS 站台
    
2.  IIS 的 ASP.NET Core Module (ANCM) 將整個站台進行複製
    
    所有檔案 (包含 `wwwroot` 資料夾) 會複製一份到 `shadowCopyDirectory` 指定的目錄中，網站會複製到該目錄的子資料夾中，資料夾名稱是一個流水號，每次陰影複製會累積複製到這裡，太舊的版本會自動刪除，所以該資料夾不需要人工維護。
    
3.  IIS 的 ASP.NET Core Module (ANCM) 會改從陰影複製資料夾啟動網站
    
    所以你原本部署的站台目錄下所有檔案就不會被鎖定，之後也可以順利的部署網站。
    

設定完成後，當你下次更新 ASP.NET Core 網站程式，IIS 的 ASP.NET Core Module (ANCM) 會自動監視到檔案變更，並且自動將網站再次複製一份到 `shadowCopyDirectory` 指定的目錄中，然後 IIS 會自動切換到新的**陰影複製**版本來執行程式，因此使用者不會感受到任何斷線時間，是不是非常棒呢！ 👍

### 相關連結

-   [ASP.NET Core updates in .NET 6 Preview 3 - .NET Blog](https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-net-6-preview-3/?WT.mc_id=DT-MVP-4015686#shadow-copying-in-iis)
-   [ASP.​NET Core in .NET 6 - Shadow-copying in IIS](https://asp.net-hacker.rocks/2021/07/19/aspnetcore6-shaddow-copy-iis.html)
-   [ASP.NET Core Module | Microsoft Docs](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-6.0&WT.mc_id=DT-MVP-4015686)
