

ASP.NET 古蹟維護 - 共享 .aspx.cs CodeFile 檔案
source link: https://blog.darkthread.net/blog/share-aspx-codefile/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

介紹一則維護上古 ASP.NET Web Site Project的實用技巧 - 讓兩個 aspx 共享一個 aspx.cs (CodeFile) 檔案。
對企業來說,靠著 Edge IE Mode 續命,IE 的 EOS 大限可以拖到 2029,但基於別把重要的事拖到重要又緊急,早早將 IE Only 翻修到可支援 Edge/Chrome/Firefox 是明智之舉。而這形成一個特殊情境 - IE Only 網頁只需修改 HTML/CSS/JavaScript 寫法即可相容 Edge/Chrome ,伺服器端邏輯可以完全不動(當然,如果趁機優化改良甚至改用 WebAPI/MVC/Razor Page/Blazor 更好,但你知道的,If it works, don't touch it),是因應 IE EOS 修改幅度最小的做法。
在開發及測試階段,新舊版本並存可方便對照,甚至上線初期平行測試,新版零星出錯時還可請使用者改用舊版頂著,爭取一些修正時間,感覺是不錯的策略,而這可善用 ASP.NET 的 CodeFile 分離特性實現。
一般來說,Web Site 站台旳每個 .aspx 會對映專屬 .aspx.cs,例如:Form.aspx 跟 Form.aspx.cs 是成對的。但實際上,這個對映關係會由 ASPX 第一行的宣告決定,預先編譯跟即時編譯有別:
<!-- 即時編譯 -->
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Form.aspx.cs" Inherits="DEMO_Form" %>
<!-- 預先編譯 -->
<%@ page language="C#" autoeventwireup="true" inherits="DEMO_Form, Dummy.DEMO" %>
搞懂這點,我們可以靠它實現一些進階應用,例如,把原本 aspx.cs CodeFile 內容搬進獨立元件程式庫 .dll 供多個網站專案共用,或是這次提到的共享 CodeFile - 讓 Form.aspx 跟 Form-Beta.aspx 的 CodeFile 都指向 Form.aspx.cs,實現兩個 aspx 共用一份後端程式的目標。
From.aspx、Form-Beta.aspx、Form.aspx.cs 的二對一結構可順利通過 Visual Studio 編譯、偵錯,將檔案直接複製到 IIS 動態編譯執行也沒問題。但如果要用 .publishproj 預先編譯發行,便會踩到地雷 - aspnet_merge.exe 在合併 DLL 時,會為每個指定 CodeFile 的.aspx 對映自己專屬的類別物件,而 DEMO\Form.aspx、DEMO\Form-Beta.aspx 指向同一個 CodeFile (Form.aspx.cs),故二者的對映類別都叫 DEMO_Form,合併時便會爆出型別名稱重複錯誤。An error occurred when merging assemblies: ILMerge.Merge: ERROR!!: Duplicate type 'DEMO_Form' found in assembly 'App_Web_srbkivbq'.
由爬文查到的討論一面倒地主張這是 CodeFile 運作機制,無解。但我不甘心放棄美妙的共享 .cs 做法,實在不想多複製一份 Form-Beta.aspx.cs (其實也沒多複雜,但我就是覺得不夠簡潔,尤其是有數十個頁面要處理時),最後我想出一招奇技淫巧,成功克服難題。
概念是這樣的,MSBuild 機制允許我們在 csproj、publishproj 裡安插自訂 Task,排在既有的編譯、複製檔案等 Task 前後執行,之前曾用過這招解決發行專案缺檔問題。(延伸閱讀:Visual Studio Publish 網站缺檔怎麼辦? 小試 MSBuild 自訂步驟)
依照這個概念,我準備兩支 PowerShell,Clean-BetaFiles.ps1 負責複雜檔案到暫存目錄到 aspnet_merge 前將 -Beta.aspx 檔案先搬到 App_Data\BetaFiles 目錄暫存不參與編譯,Copy-BetaFiles 則在發佈檔案複製到目錄資料夾後,將 App_Data\BetaFiles 暫存檔案複製回去,但第一行的 CodeFile="Form.aspx.cs" Inherits="DEMO_Form" 需改成 inherits="DEMO_Form, Dummy.DEMO",我採用的做法是 Form-Beta.aspx 的第一行換成 Form.aspx 發佈檔的第一行。
Clean-BetaFiles.ps1 及 Copy-BetaFiles.ps1 放在 App_Data 下,因此 publishproj 最後加上兩個 Target:
(AfterTargets 要怎麼決定?如何知道 $(_PreAspnetCompileMergeSingleTargetFolder)、 $(WPPAllFilesInSingleFolder)?我的做法是由 Build and Run Log 開 Diagnostic 觀察編譯 Task 步驟,再 Log 資訊去查使用的 targets 定義檔(如:Microsoft.Web.Publishing.AspNetCompileMerge.targets、Microsoft.WebSite.Publishing.targets... 不同 VS 版本可能不同) 以及官方文件)
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- 略 -->
<Target Name="RemoveBetaFormAspx" AfterTargets="CopyAllFilesToSingleFolderForAspNetCompileMerge">
<Exec Command="powershell $(_PreAspnetCompileMergeSingleTargetFolder)\App_Data\Clean-BetaFiles.ps1">
</Exec>
</Target>
<Target Name="CopyBetaFormAspx" AfterTargets="CopyAllFilesToSingleFolderForPackage">
<Exec Command="powershell $(WPPAllFilesInSingleFolder)\App_Data\Copy-BetaFiles.ps1">
</Exec>
</Target>
Clean-BetaFiles.ps1
$betaPath = "$PSScriptRoot\BetaFiles"
if (Test-Path $betaPath) { Remove-Item $betaPath -Recurse }
[System.IO.Directory]::CreateDirectory($betaPath) | Out-Null
Set-Location $PSScriptRoot\..
Get-ChildItem -Filter *-Beta.aspx -Recurse | Where-Object { $_.FullName -inotmatch 'app_data\\' } | ForEach-Object {
$src = $_.FullName
$relPath = Resolve-Path -Relative $src
$tgt = [System.IO.Path]::ChangeExtension($betaPath + $relPath, ".aspx_")
& echo F|xcopy $src $tgt /i
Write-Host "Move Beta File: $src"
Remove-Item $src
}
Copy-BetaFiles.ps1
$betaPath = Join-Path $PSScriptRoot 'BetaFiles'
$publishUrl = Join-Path $PSScriptRoot '..'
Set-Location $betaPath
Get-ChildItem -Filter *.aspx_ -Recurse | ForEach-Object {
$src = $_.FullName
$relPath = Resolve-Path -Relative $src
$tgt = (Join-Path $publishUrl $relPath).Replace('.aspx_', '.aspx');
$ref = $tgt.Replace('-Beta.aspx', '.aspx')
$rep1stLine = Get-Content $ref | Select-Object -First 1
Get-Content $src | ForEach-Object {
if ($rep1stLine) {
Write-Output $rep1stLine
$rep1stLine = $false
}
else { Write-Output $_ }
} | Out-File -FilePath $tgt
Write-Host "Copy Beta File: $tgt"
}
Set-Location $PSScriptRoot
Remove-Item $betaPath -Recurse
Remove-Item "$PSScriptRoot\*-BetaFiles.ps1"
經過一番魔改,這個違反 ASP.NET CodeFile 規則的做法就能成功編譯發佈。
發佈檔案結構如下,成功!
因為專案結構有點小複雜,我放了一份可執行範例在 Github,有需要的朋友請自取測試。(註:我測試的環境是 VS2019,.publishproj 裡 RemoveBetaFormAspx 及 CopyBetaFormAspx 的 AfterTargets 及路徑變數可能需視 VS 版本修改。)
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK