Setting up CI for ASP.NET Core Web Apps from VS Team Services to Azure

It's a fact: Humans suck at routine jobs, but machines thrive at it.

This led me to try and automate everything as far as possible. Although Continuous Integration isn't a new term, getting it to play nice with the latest & greatest technologies like ASP.NET 5 (vNext) Core 1.0, Visual Studio Online Team Services with Git and Azure can become quite messy - but it turns out, adding a bit of PowerShell makes everything better.

This tells my story on all the problems I faced and finally succeeded in deploying a ASP.NET Core Web App to Azure using a PowerShell script. The final version of the script I used in Visual Studio team Services is on the bottom of this post.

For the past year I have had the opportunity to work on a project without any rules or constraints on technology choices, nor working with clients with petty policies of using tech still in beta.

At the time, ASP.NET Core 1.0 (formerly vNext or ASP.NET 5) was just starting to take shape and it was a great opportunity to use this brand new framework on one of our web apps. What could possibly go wrong?

The source code is hosted on Git in Visual Studio Team Services and is using the new build system called Build vNext. Although one can author quite flexible build processes there, the one thing that isn't support yet is the ability to deploy ASP.NET Core Web Apps, which was exactly what I needed.

Luckily, after scouting for hours on the www, I finally came across a great article by Devon Burriss detailing how to get this working using only two PowerShell scripts - I strongly recommend having a read.

We used DNX Beta 5 at the time and everything was deploying perfectly. Keeping in mind that ASP.NET Core was still in infant stages, things tend to change quite often. It was crucial to keep up with the latest version so we don't run into massive migrations later when it hits RTM.

So, like any developer, I've made the bold decision of upgrading to the latest RC1-Update 1, just because and mainly for major performance improvements. To my surprise, there where breaking changes.

After investigation it was clear that the error was caused by setting a value in the web.config after calling dnu publish and as from Beta 8, setting these special values for DNX wasn't necessary any more, so I've removed them.

This time it was a green build and everything seemed like it went smoothly, until I noticed that the web app failed to start up on Azure. The root cause was that the path to DNX wasn't set in site\approot\web.cmd. This could easily be fixed by a simple find and replace, but I'm all about automation; remember?

One of the comments on Devon's blog post suggested a code snippet that one can append to his original Update.ps1 script that does a simple find & replace among other things. After adding the fix, everything deployed and worked again as expected. Yay! Here's the full final script (kudos to Devon & Michael):

 Param(
    [Parameter(Mandatory = $true)]
    [string]$sourceDir,
    [string]$branch = "master"
    )

 # bootstrap DNVM into this session.
&{$branch;iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/aspnet/Home/dev/dnvminstall.ps1'))}

# load up the global.json so we can find the DNX version
$globalJson = Get-Content -Path $PSScriptRoot\global.json -Raw -ErrorAction Ignore | ConvertFrom-Json -ErrorAction Ignore

if($globalJson)  
{
    $dnxVersion = $globalJson.sdk.version
}
else  
{
    Write-Warning "Unable to locate global.json to determine using 'latest'"
    $dnxVersion = "latest"
}

# install DNX
# only installs the default (x86, clr) runtime of the framework.
# If you need additional architectures or runtimes you should add additional calls
# ex: & $env:USERPROFILE\.dnx\bin\dnvm install $dnxVersion -r coreclr
& $env:USERPROFILE\.dnx\bin\dnvm install $dnxVersion -Persistent

 # run DNU restore on all project.json files in the src folder including 2>1 to redirect stderr to stdout for badly behaved tools
Write-Host "===== RESTORE ====="  
Get-ChildItem -Path $PSScriptRoot\src -Filter project.json -Recurse | ForEach-Object { & dnu restore $_.FullName 2>1 }

 # run DNU build on all project.json files in the src folder including 2>1 to redirect stderr to stdout for badly behaved tools
#Write-Host "===== BUILD ====="
#Get-ChildItem -Path $PSScriptRoot\src -Filter project.json -Recurse | ForEach-Object { & dnu build $_.FullName 2>1 }

 # run DNU publish on all project.json files in the src folder including 2>1 to redirect stderr to stdout for badly behaved tools
Write-Host "===== PUBLISH ====="  
Get-ChildItem -Path $PSScriptRoot\src -Filter project.json -Recurse | ForEach-Object { & dnu publish $_.FullName 2>1 -o "./pub" }

# workaround for what seems a bug in dnu not copying runtimes
Write-Host ("Copy runtimes to {0}\approot" -f $sourceDir)  
Copy-Item $env:USERPROFILE\.dnx\runtimes $sourceDir\approot -Recurse

# -- Removed web.config update ---

# New find & replace script
$dnxFolderString = "SET DNX_FOLDER=" + "dnx-clr-win-x86." + $dnxVersion
$webCmdFile = Get-ChildItem ($sourceDir + "\approot\web.cmd")
(Get-Content $webCmdFile) | Foreach-Object {$_ -replace "^SET DNX_FOLDER=$", $dnxFolderString} | Out-File $webCmdFile
(Get-Content $webCmdFile) | Foreach-Object {$_ -replace "--configuration Debug web ", "--configuration Release web "} | Out-File $webCmdFile

# This converts the file to UTF8 without BOM
$fileContents = Get-Content $webCmdFile
[System.IO.File]::WriteAllLines($webCmdFile, $fileContents)

Unfortunately there will not be a fix to address this issue in DNX as it has been deprecated and will be moved to the new .NET CLI, which will hopefully contain a more streamlined, less hacky approach - but for now, this is a good workaround.