I’ve been catching up with Azure Functions recently, and one thing I learned is that setting up your development environment, project structure and Azure DevOps pipeline is not as straightforward as I’d expect. The goal of this article is to help you with that.
I recommend that you first read the guidelines for running C# Azure Functions in an isolated process – the linked article is a good starting point, but it’s not perfect; specifically it doesn’t mention the packages required to use the provided code snippets.
This article assumes version 4.x of Functions and .NET 6.0 Out-of-process (isolated). You can read more about the different hosting models here.
Runtime Version | In-Process | Out-of-process |
Functions 4.x | .NET 6.0 | .NET 6.0 .NET 7.0 (preview) .NET Framework 4.8 |
Functions 3.x | .NET Core 3.1 | |
Functions 2.x | .NET Core 2.1 | |
Functions 1.x | .NET Framework 4.8 |
Setting Up the Local Development Environment
Packages
The first thing I recommend you do is update all packages to the newest versions – it took me good two hours of googling to realize that my project wasn’t working only because the Rider template came with some outdated packages. Hopefully you can learn from my mistake and save yourself some time.
Visual Studio vs Rider Considerations
In both Visual Studio and JetBrains Rider, creating a new Azure Functions isolated project is almost a no-brainer. Almost. By default Rider prompted me to install the legacy Azure Storage Emulator (ASE), even though it came with the new Azurite emulator meant to replace its legacy predecessor. When I uninstalled ASE, it took me a while to figure out why my TimerTrigger (which I mention later) didn’t work in Rider – interestingly my HttpTrigger worked just fine. As it turns out, in Rider you have to start Azurite manually if you want to run functions in localhost (Tools > Azure > Start Azurite), while Visual Studio does this for you automatically.
Host Builder
Take a look at my host builder and the description that follows.
Application Insights
You will need to use the package Microsoft.Azure.Functions.Worker.ApplicationInsights which doesn’t yet have an official release. I am using 1.0.0-preview2 and it’s the only one that comes closest to supporting the full spectrum of AppInsights features – at least for out-of-process functions.
Local Settings
Local settings such as connection strings are kept in the local.settings.json file. You DO NOT commit that file to git or you use it for your CI/CD pipelines (I describe deployment is later). This file follows the same convention as the well-known appsettings.json file.
Please keep in mind that the second parameter to AddJsonFile is both optional and is called optional. Now, if you set it to false, the azure will fail to start the worker process. If that confuses you, you’re not the only one. It doesn’t make much sense, given you’re not supposed to commit local.settings.json. So, always set it to true.
Database Setup
No tricks were required for this. It works the same for both in- and out-of-process functions.
Dependency Injection
If you’re coming from the world of ASP.NET Core apps, the dependency injection model for out-of-process functions works and feels the same as you’re used to in ASP.NET Core. However, it is different for in-process functions–one reason you don’t want to go that way.
First Run
Once you have your Azurite running (remember: VS runs it for you) you can go ahead and run your functions. You should see something similar to the following:
HttpTrigger and TimerTrigger I used, were generated by Rider, and they do nothing special other than logging. I do feel inclined to tell you however, that Azure Functions use the Spring cron format which is 6 figures long, not the 5 figure format of the typical Linux cron you might be more familiar with.
Setting Up Azure DevOps Pipelines
Build Pipeline
The build pipeline that I used came directly from the DevOps template (you can generate it there). I have changed the vmImageName to windows-2022 and disabled the Deploy stage as I wanted to setup a separate release pipeline.
trigger:
- main
variables:
# Azure Resource Manager connection created during pipeline creation
azureSubscription: '████████-████-████-████-████████████'
# Function app name
functionAppName: 'ShantiNewFunctionApp'
# Agent VM image name
vmImageName: 'windows-2022'
# Working Directory
workingDirectory: '$(System.DefaultWorkingDirectory)/AF.API'
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: $(vmImageName)
steps:
- task: [email protected]
displayName: Build
inputs:
command: 'build'
projects: |
$(workingDirectory)/*.csproj
arguments: --output $(System.DefaultWorkingDirectory)/publish_output --configuration Release
- task: [email protected]
displayName: 'Archive files'
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)/publish_output'
includeRootFolder: false
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
replaceExistingArchive: true
- publish: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
artifact: drop
Release Pipeline
The release pipeline is incredibly simple, you only need two steps. Please notice that in the second task I am injecting a connection string that is using DefaultConnection variable – you might need one if you intend to use a database as well.
- task: [email protected]
displayName: 'Deploy Azure Function App'
inputs:
azureSubscription: '$(Parameters.AzureSubscription)'
appType: '$(Parameters.AppType)'
appName: '$(Parameters.AppName)'
- task: [email protected]
displayName: 'Azure App Service Settings: <name>'
inputs:
azureSubscription: '<subscription>'
appName: <appName>
resourceGroupName: functions
connectionStrings: |
[
{
"name": "DefaultConnection",
"value": "$(DefaultConnection)",
"type": "SQLAzure",
"slotSetting": false
}
]
Summary
I hope some of my discoveries help save you some time getting started with Azure Functions in its current state today.
It is clear that the out-of-process model of Azure Functions is where Microsoft is going, hence I recommend you choose it if at all possible. You might encounter a problem with packages and will have to accept the way new functions are configured (local.settings.json) but overall it’s very similar to ASP.NET Core, so the learning curve isn’t too difficult.