The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

ASP.NET MVC 建置部署套件後無法 MvcBuildViews 的解法

我去年寫過一篇【Visual Studio 2010 建置部署套件與 ASP.NET MVC 的衝突】文章,雖然當時已經知道怎麼解決,但是每次在 發行網站 (Publish) 或 建置部署套件 (Build Deployment Package) 後都要手動刪除 obj 目錄實在覺得很麻煩,今天狠下心研究出一個方法可以在 MvcBuildViews 之前「自動」刪除 obj 目錄裡那些會阻礙建置步驟的相關檔案。

我們的需求很簡單,只要在 Build 之前把 obj 目錄刪除即可,你可以在執行建置之前手動執行清除命令,這個動作會把所有 obj 目錄下的檔案都清空,也可以達成目的,但多了一個手動的步驟就是不夠完美。

是為了要透過 MSBuild 的設定來自動化刪除 obj 目錄,我嘗試了好多種方法,例如我曾經嘗試在 BeforeCompile 目標加入刪除 obj 目錄的 MSBuild 工作,但是這樣一來會引發以下錯誤:

嚴重錯誤 CS0042: 建立偵錯資訊檔案 'G:\Projects\Web\obj\Release\Web.PDB' 時發生未預期的錯誤 -- 'G:\Projects\Web\obj\ReleaseWeb.pdb: 系統找不到指定的路徑。

這代表 Visual Studio 2010 在正式開始編譯專案前,會先將一些偵錯資訊檔案整理在 obj 目錄下,在這個時間點你不能異動關於這個目錄的任何檔案,否則很容易就會出現編譯失敗的情況。

後來靈機一動直接把刪除 obj 的工作寫在 MvcBuildViews 目標裡,在 AspNetCompiler 工作的前面,果然就成功了!

嘗試成功後,我發現在 MvcBuildViews 目標有定義一個 AfterTargets 屬性,這是 MSBuild 4.0 新增的屬性 ( Visual Studio 2010 使用 MSBuild 4.0 ) 代表此目標會在 AfterBuild 之後執行,這也代表著你只要將工作寫在 AfterBuild 目標即可。我個人覺得這樣寫比較漂亮,不要動到原本的 MvcBuildViews 目標設定,但其實以下這段跟上面這段可以達到完全相同的目的:

而之後在建置完成後的確不會再出現失敗的情況,但你的 obj 目錄在建置之後就會被清空的跟嬰兒的屁股一樣光亮無暇,你想查詢建置過程中所產生的中繼檔案就沒辦法了,所以最完美的情況下應該只刪除那些「建置部署套件」所產生的檔案與目錄就好,而不是整個把 obj 目錄給砍掉。

我們沿用上述的設定,一樣直接在 AfterBuild 目標把所有 obj 下的檔案全部砍光,然後執行到「建置部署套件」這個步驟時,Visual Studio 2010 所在 obj 目錄下所產生的目錄與檔案就是我們要刪除的目標了!

在微軟內建的 MSBuild Tasks 中,有個 Delete Task 可以刪除單一檔案,但不能使用萬用字元,必須預先知道檔名才能刪除,在我們的例子中,我們會需要這個 Delete Task 來幫我們刪除 CSAutoParameterize.parameters.xml 檔案。

另外還有一個內建的 RemoveDir Task 可以用來刪除目錄,不過這個 Task 確有一個嚴重的缺陷,那就是他只能刪除一個「空的目錄」,裡面只要有檔案或子目錄就會引發例外狀況而中斷建置過程,所以我們必須找到一個方法能將所有目錄下的檔案與子目錄全部都刪除的方法。

為了能刪除目錄下所有檔案,我找到一個很棒的 MSBuild Extension Pack 套件,他自訂了許多非常好用的 MSBuild Tasks,其中就包含了刪除目錄下所有檔案與子目錄的 MSBuild.ExtensionPack.FileSystem.Folder Task,以下是安裝與設定的步驟說明:

1. 下載 MSBuild Extension Pack:請下載 MSBuild Extension Pack 4.0.3.0 Installers

2. 下載後是一個 MSBuild Extension Pack 4.0.3.0 Installers.zip 壓縮檔,裡面有兩個 MSI 安裝檔,請安裝 x86 的版本,也就是 MSBuild Extension Pack 4.0.msi因為 Visual Studio 2010 是 x86 版本

3. 然後就可以修改你在 Visual Studio 2010 裡面的專案檔,主要是匯入 MSBuild.ExtensionPack.tasks 工作定義,以及設定 AfterBuild 目標,以下直接、剪下貼上即可,我都寫成自動變數了:

<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks"/>

<Target Name="AfterBuild">

  <MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent" Path="$(WDTargetDir)obj\$(ConfigurationName)\CSAutoParameterize" Condition=" Exists('$(WDTargetDir)obj\$(ConfigurationName)\CSAutoParameterize') "/>
  <RemoveDir Directories="$(WDTargetDir)obj\$(ConfigurationName)\CSAutoParameterize" />

  <MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent" Path="$(WDTargetDir)obj\$(ConfigurationName)\TransformWebConfig" Condition=" Exists('$(WDTargetDir)obj\$(ConfigurationName)\TransformWebConfig') "/>
  <RemoveDir Directories="$(WDTargetDir)obj\$(ConfigurationName)\TransformWebConfig" />

  <MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent" Path="$(WDTargetDir)obj\$(ConfigurationName)\Database" Condition=" Exists('$(WDTargetDir)obj\$(ConfigurationName)\Database') "/>
  <RemoveDir Directories="$(WDTargetDir)obj\$(ConfigurationName)\Database" />

  <MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent" Path="$(WDTargetDir)obj\$(ConfigurationName)\Package" Condition=" Exists('$(WDTargetDir)obj\$(ConfigurationName)\Package') "/>
  <RemoveDir Directories="$(WDTargetDir)obj\$(ConfigurationName)\Package" />

  <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\CSAutoParameterize.parameters.xml" />

</Target>

如果你還是希望單純的清空 obj 目錄的話,可以用比較簡短的寫法如下:

<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks"/>

<Target Name="AfterBuild">
  <MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent"
      Path="$(WDTargetDir)obj\$(ConfigurationName)"
      Condition=" Exists('$(WDTargetDir)obj\$(ConfigurationName)') "/>
</Target>

 

簡單三個步驟:下載、安裝、設定,就能讓你的 ASP.NET MVC 開發、建置、部署一連串的作業更加的流暢,完美! ^__^

 

2011/09/15 補充

由於要額外安裝 MSBuild Extension Pack 比較麻煩,如果有人剛 Check out 專案不知道要安裝 MSBuild Extension Pack 的話可能就會立即卡住,以下是我另外寫過的設定檔,也可以達到本篇文章的目的:

<Target Name="AfterBuild">
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\CSAutoParameterize\original\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\CSAutoParameterize\transformed\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\TransformWebConfig\original\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\TransformWebConfig\transformed\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\WebConfigTransformFolderForAzureAuthentication\original\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\WebConfigTransformFolderForAzureAuthentication\transformed\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\InsertEFCodeFirstDeploy\original\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\InsertEFCodeFirstDeploy\transformed\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\TransformWebConfig\assist\Web.config" />  
    <Delete Files="$(WDTargetDir)obj\$(ConfigurationName)\Package\PackageTmp\Web.config" />  
</Target>

一般來說我只會在 Release 方案設定進行發佈動作,不過如果你連 Debug 也要發佈網站或建置部署套件的話,有可能在 obj\Debug 裡面也會出現一些會影響 MvcBuildViews 的動作,你必須把 obj\ 以下所有的 web.config 都刪除才行,所以如果你的方案設定只有 Debug 與 Release 的話,也可以考慮以下設定:

<Target Name="AfterBuild">
    <Delete Files="$(WDTargetDir)obj\Debug\CSAutoParameterize\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\CSAutoParameterize\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\TransformWebConfig\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\TransformWebConfig\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\WebConfigTransformFolderForAzureAuthentication\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\WebConfigTransformFolderForAzureAuthentication\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\InsertEFCodeFirstDeploy\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\InsertEFCodeFirstDeploy\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\TransformWebConfig\assist\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Debug\Package\PackageTmp\Web.config" />

    <Delete Files="$(WDTargetDir)obj\Release\CSAutoParameterize\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\CSAutoParameterize\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\TransformWebConfig\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\TransformWebConfig\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\WebConfigTransformFolderForAzureAuthentication\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\WebConfigTransformFolderForAzureAuthentication\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\InsertEFCodeFirstDeploy\original\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\InsertEFCodeFirstDeploy\transformed\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\TransformWebConfig\assist\Web.config" />
    <Delete Files="$(WDTargetDir)obj\Release\Package\PackageTmp\Web.config" />
</Target>

 

相關連結

留言評論