Blog

Octopus Deploy precompile ASP.NET application
 

Today, we’ll take look at various ways to pre-compile your ASP.NET application, while integrating with OctopusDeploy.

Option A) Precompile when you deploy to machine in Octopus

This is probably the easiest. All you need to do is add new step in octopus(Run a PowerShell script), that does the pre-compile once the package has been deployed. This is in-place precompilation. This worked for me:

$appDestinationPath = $OctopusParameters['WebsiteBasePath'] + $OctopusParameters['WebsiteFolder']
& $env:windir\microsoft.net\framework64\v4.0.30319\aspnet_compiler.exe -v / -p $appDestinationPath -errorstack | write-host

Pros:

  • Very Simple
  • Perfect if you have only one machine you need to deploy to.
  • Can wrap it into a octopus deploy step, and it’s easily reusable by anyone.

Cons:

  • Can be much, much slower when you have multiple websites you need to deploy to
  • Not a good practice, precompile should be part of build process.

Option B) Precompile as part of the build process before OctoPack runs

It was not too easy, if anyone knows a better way, I am all ears. That said, assume that you want to precompile project called MySite.csproj, before it is packed / pushed to Octopus.

First, create MySite.nuspec, add it to the same folder as the project file. The important thing here is “files” element, which instructs OctoPack to pick up the only the files on that specific folder, called “precompiled_output”.

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>MySite</id>
    <title>MySite</title>
    <version>0.0.0.0</version>
    <authors>MySite</authors>
    <description>This package contains the MySite site</description>
  </metadata>

  <files>
    <file src="precompiled_output\**\*.*" target=""/>
  </files>
</package>

Now, it’s time to open your project file, and actually edit it. We have to modify it such that our task runs only if RunOctoPack msbuild parameter is passed in, and before the target OctoPack has chance to run.

What we will do in this target, is collect all the files, write them to CollectedFiles.txt, and then invoke our PowerShell script, which does the precompile.

Add this target after you’ve imported the OctoPack.targets in your project file, not that the order should matter theoretically, but that’s what I am doing:

<!--Precompile everything before OctoPack gets chance to run! -->
<Target Name="Precompile" BeforeTargets="OctoPack" Condition="$(RunOctoPack)">
	<!-- Collect all files -->
	<ItemGroup>
	  <CollectedFiles Include="@(FileWrites)" Exclude="$(IntermediateOutputPath)**\*" />
	  <CollectedFiles Include="@(FileWritesShareable)" Exclude="$(IntermediateOutputPath)**\*" />
	  <CollectedFiles Include="@(Content)" />
	  <CollectedFiles Include="@(TypeScriptCompile)" />
	</ItemGroup>
	
	<Message Text="Precompiling everything" Importance="high" />
	
	<!--Write everything to disk for powershell. -->
	<WriteLinesToFile File="$(ProjectDir)CollectedFiles.txt" Lines=";@(CollectedFiles, ',')" 
		Overwrite="true" 
		Encoding="Unicode" />
	
	<Exec Command="powershell -ExecutionPolicy unrestricted -File &quot;$(ProjectDir)precompile.ps1&quot; -projectDir &quot;$(ProjectDir)\&quot;" 							
			LogStandardErrorAsError="true" 
			ContinueOnError="false" 
			ConsoleToMSBuild="true">
	  <Output 
		TaskParameter="ConsoleOutput" 
		PropertyName="OutputOfExec" />
	</Exec>
</Target>

Why am I collecting all the files to text file, and then executing PowerShell script? Mainly because I know too little MsBuild. I almost had it, but items inside “ItemGroup” are all scrambled, some of them have relative paths, some of them don’t. If I were to access ‘%(RelativeDir)’, some of the files contained absolute path, which made no sense.

Also, you can’t pass list of the files through command line, as PowerShell has maximum input parameter length.
Now, the actual PowerShell script that does the precompile. It doesn’t do only precompile, but makes sure all paths are relative, which makes it easy to copy them to “precompiled_intermediate” folder.

Call it precompile.ps1, and place it next to your project file.

# Precompiles everything for OctoPack.
param([string]$projectDir)

$projectDir = $projectDir.TrimEnd("\\")
$intermediateOutputDir = "$projectDir\precompiled_intermediate"
$outputDir = "$projectDir\precompiled_output"
$files = (Get-Content "$projectDir\CollectedFiles.txt") -split ','

Try 
{
	Push-Location $projectDir
	Remove-Item -Recurse -Force $intermediateOutputDir -ErrorAction SilentlyContinue
	Remove-Item -Recurse -Force $outputDir -ErrorAction SilentlyContinue
	
	New-Item $intermediateOutputDir -Type directory | Out-Null
	New-Item $outputDir -Type directory | Out-Null
	
	foreach($file in $files) {
		# turn an absolute / relative path to relative.
		# bin/obj/LinKExternalAccounts.cshtml => bin/obj/LinKExternalAccounts.cshtml 
		# C:/src/m/bin/obj/LinKExternalAccounts.cshtml  => bin/obj/LinKExternalAccounts.cshtml 
		$relativeFilePath = Resolve-Path -relative $file
		
		$destinationFilePath = "$intermediateOutputDir\$relativeFilePath"
		
		# have to touch the item first, otherwise it doesn't work.
		New-Item -ItemType File -Path $destinationFilePath -Force -ErrorAction SilentlyContinue | Out-Null
		Copy-Item $projectDir\$relativeFilePath $destinationFilePath  -ErrorAction SilentlyContinue | Out-Null
	}
	
	# If everything goes well,
	# invoke the aspnet_compiler and hope for the best.
	& $env:windir\microsoft.net\framework64\v4.0.30319\aspnet_compiler.exe -v / -p $intermediateOutputDir -errorstack $outputDir | Write-Host
} Finally 
{
	Pop-Location
}

Pros:

  • Done only once, can be deployed to many websites.
  • Part of the build process. If precompile fails, your build process fails. (Catching errors early)
  • Does not depend on your build server. You can use TFS / teamCity, etc, and it should all work the same.

Cons:

  • Quite complex, atleast implementation wise.

 

2 comments

Reply

Your email addres will not be published. All fields are required.