Posted in:

Several years ago I rewrote this blog in ASP.NET Core, but I kept on using my existing deployment technique of using Kudu, so I could push my changes to a Git repo hosted in Azure Repos, and my Azure Web App would build and publish the code.

For the most part it's worked flawlessly, but this weekend it stopped working for no apparent reason, with the following error:

The SDK resolver "Microsoft.DotNet.MSBuildSdkResolver" failed while attempting 
to resolve the SDK "Microsoft.NET.Sdk.Web". Exception: 
"System.IO.DirectoryNotFoundException: Could not find a part of 
the path 'D:\Program Files (x86)\dotnet\sdk-manifests'.,,0 kudu

I decided it was about time that I switched to Azure Pipelines, the obvious choice since my Git repo is hosted in Azure DevOps.

Creating a pipeline

Creating an Azure Pipeline is very easy in the DevOps portal, just visit the Pipelines tab for your project and select "New Pipeline" which launches a step by step guide, starting with selecting where your code is (an Azure Repo), and selecting a starting template (the ASP.NET Core one was the obvious choice).

This creates an azure-pipelines.yml file and checks it into your Git repo for you.

Build and publish steps

The default template gets you started with a single step that calls dotnet build in release mode whenever a change is checked into the master branch.

- master

  vmImage: ubuntu-latest

  buildConfiguration: 'Release'

- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'

Next, we need to publish the website to a zip. For this I used the .NET Core CLI task.

I overrode the arguments to specify the build configuration I wanted (Release).

Here's the YAML for my publish step:

- task: DotNetCoreCLI@2
    command: 'publish'
    arguments: '--configuration $(buildConfiguration) -o $(Build.ArtifactStagingDirectory)/Output'
    publishWebProjects: true

Note that I also explicitly set the location of the output with the -o argument, as my website includes some zip files as static assets, which meant the final stage of my pipeline didn't know which was the zip that needed deploying to App Service.

##[error]Error: More than one package matched with specified pattern: 
/home/vsts/work/1/s/**/*.zip. Please restrain the search pattern.

Deploy to Azure App Service

To deploy to Azure App Service, we first need to create a "Service Connection" in Azure DevOps. This is a pretty straightforward process - just go to "project settings | service connection" and select "Azure Resource Manager". From there you can select the Azure Subscription you want to use, and let it automatically create a Service Principal to use for deployment.

The deployment is handled by the Azure Web App task. We need to specify the name of our service connection, the name of the webapp we want to deploy to (which has to be in the chosen subscription for the Service Connection), and the package needs to point to the same location chosen for the publish task.

- task: AzureWebApp@1
    azureSubscription: 'My Service Connection'
    appName: 'mywebapp'
    package: '$(Build.ArtifactStagingDirectory)/Output/**/*.zip'

And that's all there is to it. Note that the deploy doesn't work immeditely after creating a brand new webapp in Azure - it seems to need a few minutes to become visible.

And the nice thing about using Azure Pipelines for the build compared with Kudu, is that we're not reliant on the Azure App Service itself to perform the builds for us. Another advantage it has over Kudu is that we get a much nicer UI to view the logs of each run of the pipeline, making it much easier to troubleshoot.


Comment by Thomas Levesque

Just wondering, what is your ASP.NET Core app doing exactly? As far as I can tell, your blog only has static content.
I switched to a static website generator (Hugo) after years of using Wordpress, and I'm not going back! Better performance, lower cost (I only pay for the domain), less maintenance, simpler workflow (just git push markdown files, and a Github action builds and publishes the website).

Thomas Levesque
Comment by Mark Heath

Absolutely agree it should be a static website! There are a few small bits of server-side code, but nothing that couldn't be worked around. I've been playing with Hugo, and at some point I'd like to switch over. Would be a fair amount of work to get everything converted though. Would like to get rid of Disqus too and just use GitHub discussions per post as well

Mark Heath