最近手頭的一個ASP.Net的項目不時會出現編譯錯誤,提示某個控制項ascx類不存在,但該控制項明明就在同一個網站下。錯誤有時候會突然出現,而一旦出現錯誤再多次編譯也無法消除。由於公司使用指令碼編譯,而不是用msbuild或Visual Studio直接編譯。事實上用Visual Studio編譯從來沒有出過錯。用ProcExp看了一下build時編譯時間到底執行了什麼:
aspnet_compiler.exe -d -v / -p <source webroot> <target webroot>
主要就是這一條命令。難道是編譯器有bug?再細究發現aspnet_compiler.exe又調用了csc.exe(C#編譯器):
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe" /noconfig /fullpaths @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\1c85b622\f7798d18\mz_xtoba.cmdline"
到Temporary ASP.NET Files\root\1c85b622\f7798d18目錄下查看,發現很多.cmdline, .err, .out, .compiled等檔案,還有很多.cs檔案!原來那些網頁或AppCode下的.cs檔案在這裡被重組並分組了:App_Code.n.cs; App_Web_<xxx>.n.cs; (n=0~...)。如果網頁比較多的話,就會有多組App_Web_<xxx>.n.cs。編譯器會一組一組地編譯n個cs檔案,然後下一組的cs編譯會引用上一組編譯產生的DLL(在.cmdline裡):
/t:library /utf8output /R:"System.Web.Services.dll" /R:"App_global.asax.dll" /R:"App_Web_rydiwb8f.dll" /out:"App_Web_bl8pd2er.dll" App_Web_bl8pd2er.9.cs App_Web_bl8pd2er.0.cs (已省略一些路徑和參數。這裡面App_Web_bl8pd2er.dll就引用了上一組cs產生的App_Web_rydiwb8f.dll)
但怎麼決定檔案的分組呢,哪些檔案要放在前面的組,哪裡檔案要放在後面?我們在.aspx.xxx.compiled或.ascx.xxx.compiled裡發現了該檔案依賴於哪些檔案的資訊:
<?xml version="1.0" encoding="utf-8"?>
<preserve resultType="3" virtualPath="/Result.aspx" hash="84d987223" filehash="a23fecda9fe4d6d8" flags="110000" assembly="App_Web_wt3qqawy" type="ASP.result_aspx">
<filedeps>
<filedep name="/controls/Control1.ascx" />
<filedep name="/controls/Control1.ascx.cs" />
<filedep name="/Result.aspx" />
<filedep name="/Result.aspx.cs" />
</filedeps>
</preserve>
於是猜想編譯器就是在記憶體中維護用這樣的依賴關係,並可以產生一個樹來決定檔案分組先後。但.compiled檔案只在編譯成功那個頁面後才會產生。
明白了這些,再來找項目的問題:原來Result.aspx.cs裡動態建立了Control2:
Control2 ctrl = (Control2)page.LoadControl("controls\\Control2.ascx");
page.PlaceHolder.Controls.Add(ctrl);
但在.compiled檔案裡看不到有這樣的依賴關係!所以編譯器不知道要把Control2.ascx分在Result.aspx的前一組。這樣就會產生有時可以編譯通過(剛好分對組了,上面的.compiled檔案就是碰巧編譯成功時的樣子)有時出錯的奇怪問題。解決方案也不難,就是在Result.aspx頁面註冊一下Control2控制項:<%@ Register TagPrefix="uc1" TagName="Control2" Src="controls\Control2.ascx" %>
即使aspx頁面裡沒有直接引用該控制項也要加這一行,讓編譯器知道該頁面引用了控制項。而檔案依賴關係和.compiled檔案正是使用了這個資訊,編譯器根本不看.cs檔案裡的引用。