開發測試 ASP.NET 專案我有個常用的小技巧 - 將 publish 輸出目的地設成 IIS 應用程式主目錄。如此不用啟動 Visual Studio 也能測試,每次修改程式後,使用指令 dotnet publish -c Release 或在 Visual Studio 執行 Publish 便能換版。

在實務應用時會遇到一個問題,若更新時有人剛瀏覽過網站,IIS 網站應用程式在啟動中,WebAppName.dll 將會被鎖定無法覆寫,導致發佈失敗!

ASP.NET 時代,我們可以隨時覆寫 bin 目錄的 DLL 觸發 IIS AppPool 重啟;在 ASP.NET Core 則必須先停止 AppPool 才能覆寫 DLL 檔。關於這個議題,更多細節可參考保哥這篇 - 如何正確地發行 ASP.NET Core 網站到遠端 IIS 站台,除了使用 PowerShell 先停止 AppPool 再複製檔案,更簡便的的做法是先在網站根目錄下放一個 app_offline.htm 檔案,IIS 在偵測到檔案出現便會讓網站應用程式離線,AppPool 停止後即可覆寫檔案,更新完成後再刪除 app_offline.htm 讓網站上線。

若要讓這一連串動作一氣喝成,可在專案根目錄放個 Publish.ps1,用它取代單純的 dotnet publish 指令:

$pubFolder = "$PSScriptRoot\bin\Release\net6.0\publish"
$appOfflineHtm = "$pubFolder\app_offline.htm";
New-Item $appOfflineHtm -ItemType "file"
dotnet publish -c Release
Remove-Item $appOfflineHtm

但這個做法,變成得手動執行,若是在 Visual Studio 按 Publish 還是會卡關。

於是,我再改良了做法,把放 app_offline.htm 跟刪掉 app_offline.htm 寫成 Publish 前後的事件 (CustomActionsBeforePublish 及 CustomActionsAfterPublish):

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <Target Name="CustomActionsBeforePublish" BeforeTargets="BeforePublish">
	<CreateProperty Value="$(ProjectDir)$(PublishDir)app_offline.htm">
      <Output TaskParameter="Value" PropertyName="AppOfflinePath"/>
	</CreateProperty>
    <Exec Command="echo UPDATING > &quot;$(AppOfflinePath)&quot;"></Exec>
    <!--<Exec Command="ping -n 3 localhost > nul"></Exec>-->
  </Target>
  <Target Name="CustomActionsAfterPublish" BeforeTargets="AfterPublish">
    <Exec Command="del &quot;$(AppOfflinePath)&quot;"></Exec>
  </Target>

</Project>

補充說明,實測 Visual Studio Publish 時 AfterPublish 的 $(PublishDir) 變數會變成 \obj\Release\net6.0\PubTmp\Out\,故我加了一個 CreateProperty Task 儲存 app_offline.htm 路徑;另外,IIS 偵測到 app_offline.htm 後會通知 ASP.NET Core 程式關機,但有時需等待一些善後動作完成才真的結束,在此之前檔案仍會被鎖定,Publish 本身具有重試機制,或者也可在放入 app_offline.htm 後加一個 ping -n 秒數 localhost > nul 等待一段時間再覆寫 DLL。

最後,如果是 ASP.NET Core 6 專案,目前有個實驗性質的陰影複製(Shadow-Copying)功能,能更優雅地解決執行中網站部署問題,詳情可參考保哥的文章 - 如何啟用 ASP.NET Core 6.0 部署到 IIS 的陰影複製 (Shadow-copying) 功能

【延伸閱讀】

Example of using batch and MSBuild event to create and delete app_offline.htm to solve ASP.NET Core dll lock issue in IIS.


Comments

# by Chin

请问一下前辈,我是用vscode开发ASP.Net Core,有没有办法达成以上的目的?

# by Jeffrey

to Chin, 文章提的兩種做法都適用 VSCode,按 Ctrl-Shift-` 開 Terminal 視窗,下指令 .\Publish.ps1 (批次檔) 或是 dotnet publish -c Release (MSBuild Task)。

# by Chin

to Jeffrey, 谢谢您的回复,我试看看

# by Gou

我是用powershell直接停止/啟動IIS內app pool Invoke-Command -Credential $Credential -ComputerName "Computer Name" -ScriptBlock { Stop-WebAppPool -Name "AppName" } dotnet publish Invoke-Command -Credential $Credential -ComputerName "Computer Name" -ScriptBlock { Start-WebAppPool -Name "AppName" }

# by Jeffrey

to Gou, 這也是選項,需動用管理者權限是小缺點。

# by ken

請問有沒有試過.net 7 mvc,是不是都不適用,只能停用網站再更新? 因為我試過app_offline.htm,一樣會有lock情況,還是是因為web deploy的關係? 陰影複製似乎也不支援,因為web.config也是自動產生的? 謝謝!

# by Jeffrey

to ken, 我自己沒遇過,但 MVP Rick Strahl 有提過 app_offline.htm 有可能因 Request 處理不完仍然 Lock 的狀況。 https://weblog.west-wind.com/posts/2022/Nov/07/Avoid-WebDeploy-Locking-Errors-to-IIS-with-Shadow-Copy-for-ASPNET-Core-Apps Usually this is due to the application still running pending requests or running some background operations that have not completed and released their background threads. End result: In some cases the IIS application does not unload. Shadow Copy 在 ASP.NET Core 7 已成為正式功能(原本是實驗性質),前述文章有介紹做法,可以參考看看。

# by ken

感動,謝謝回覆! 昨天晚上還有持續再找資料, 應該是專案發佈時要先把自動產生web.config設定關掉,然後再去server上修改web.config,打開陰影複製功能,並裝上hosting bundle,,再發佈看看。 不過今天颱風假,待上班後再來實作看看是不是這麼一回事。

Post a comment